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 java.util.ArrayList;
053: import java.util.Arrays;
054: import java.util.List;
055: import javax.swing.*;
056: import javax.swing.plaf.*;
057: import javax.swing.text.*;
058: import javax.swing.event.*;
059: import javax.swing.border.Border;
060: import javax.swing.plaf.UIResource;
061: import javax.swing.Timer;
062:
063: import org.openide.ErrorManager;
064: import org.openide.windows.TopComponent;
065:
066: // XXX Copied from previously located openide/src/../text/ dir, this is not a NB code.
067:
068: /* A transfer handler for the editor component. This seems necessary because
069: * the NetBeans editor doesn't inherit from the Swing plaf.basic package,
070: * so it's missing a bunch of drag & drop behavior.
071: * <p>
072: * This code is basically a merged version of text-related code in
073: * javax.swing.plaf.basic: BasicTextUI, BasicTransferable, ...
074: * I had to copy it since it has package protected access in
075: * javax.swing.plaf.basic.
076: * <p>
077: * <b>There is one important difference</b>. In order to allow the DESTINATION
078: * to decide if the transferable should be moved or copied (e.g. if the
079: * destination is the same document, move, if it's the clipboard palette,
080: * copy), there's a global flag that can be set which basically turns off
081: * moving when a drag is in progress. Yup, this is a bit of a hack, but
082: * I couldn't find a better way. With Swing, the copy-vs-move decision is
083: * made when the drag is -started-, and at that point we don't know yet
084: * where you're going to drop. The docs for TransferHandler exportAsDrag says:
085: * <blockquote> action - the transfer action initially requested; this should
086: * be a value of either <code>COPY</code> or <code>MOVE</code>;
087: * the value may be changed during the course of the drag operation
088: * </blockquote>.
089: * However, it does not say HOW you can change the action, and from looking
090: * at the code, I suspect it cannot be done, since the action is passed
091: * to a gesture listener that has private access.
092: * <p>
093: * @author Tor Norbye
094: */
095:
096: public class TextTransferHandler extends TransferHandler implements
097: UIResource {
098:
099: /** Flag which is only defined during a drag & drop operation.
100: * Clients (typically drop zones) can set it to true to indicate
101: * that the data being dragged should be copied, not moved.
102: * For example, the clipboard viewer sets this when it handles
103: * the import. That way, the exportDone method knows not to remove
104: * the text being placed on the clipboard from the document, since
105: * text dragging (without modified keys) defaults to moving, not
106: * copying. And we don't want to disallow copying in getSourceActions,
107: * since dragging text from one place in the document to another
108: * SHOULD be moved, not copied. */
109: public static boolean dontRemove = false;
110:
111: private JTextComponent exportComp;
112: private boolean shouldRemove;
113: private int p0;
114: private int p1;
115:
116: /**
117: * Try to find a flavor that can be used to import a Transferable.
118: * The set of usable flavors are tried in the following order:
119: * <ol>
120: * <li>First, an attempt is made to find a flavor matching the content type
121: * of the EditorKit for the component.
122: * <li>Second, an attempt to find a text/plain flavor is made.
123: * <li>Third, an attempt to find a flavor representing a String reference
124: * in the same VM is made.
125: * <li>Lastly, DataFlavor.stringFlavor is searched for.
126: * </ol>
127: */
128: protected DataFlavor getImportFlavor(DataFlavor[] flavors,
129: JTextComponent c) {
130: DataFlavor plainFlavor = null;
131: DataFlavor refFlavor = null;
132: DataFlavor stringFlavor = null;
133: if (c instanceof JEditorPane) {
134: for (int i = 0; i < flavors.length; i++) {
135: String mime = flavors[i].getMimeType();
136: if (mime.startsWith(((JEditorPane) c).getEditorKit()
137: .getContentType())) {
138: return flavors[i];
139: } else if (plainFlavor == null
140: && mime.startsWith("text/plain")) {
141: plainFlavor = flavors[i];
142: } else if (refFlavor == null
143: && mime
144: .startsWith("application/x-java-jvm-local-objectref")
145: && flavors[i].getRepresentationClass() == java.lang.String.class) {
146: refFlavor = flavors[i];
147: } else if (stringFlavor == null
148: && flavors[i].equals(DataFlavor.stringFlavor)) {
149: stringFlavor = flavors[i];
150: }
151: }
152: if (plainFlavor != null) {
153: return plainFlavor;
154: } else if (refFlavor != null) {
155: return refFlavor;
156: } else if (stringFlavor != null) {
157: return stringFlavor;
158: }
159: return null;
160: }
161:
162: for (int i = 0; i < flavors.length; i++) {
163: String mime = flavors[i].getMimeType();
164: if (mime.startsWith("text/plain")) {
165: return flavors[i];
166: } else if (refFlavor == null
167: && mime
168: .startsWith("application/x-java-jvm-local-objectref")
169: && flavors[i].getRepresentationClass() == java.lang.String.class) {
170: refFlavor = flavors[i];
171: } else if (stringFlavor == null
172: && flavors[i].equals(DataFlavor.stringFlavor)) {
173: stringFlavor = flavors[i];
174: }
175: }
176: if (refFlavor != null) {
177: return refFlavor;
178: } else if (stringFlavor != null) {
179: return stringFlavor;
180: }
181: return null;
182: }
183:
184: /**
185: * Import the given stream data into the text component.
186: */
187: protected void handleReaderImport(Reader in, JTextComponent c,
188: boolean useRead) throws BadLocationException, IOException {
189: if (useRead) {
190: int startPosition = c.getSelectionStart();
191: int endPosition = c.getSelectionEnd();
192: int length = endPosition - startPosition;
193: EditorKit kit = c.getUI().getEditorKit(c);
194: Document doc = c.getDocument();
195: if (length > 0) {
196: doc.remove(startPosition, length);
197: }
198: kit.read(in, doc, startPosition);
199: } else {
200: char[] buff = new char[1024];
201: int nch;
202: boolean lastWasCR = false;
203: int last;
204: StringBuffer sbuff = null;
205:
206: // Read in a block at a time, mapping \r\n to \n, as well as single
207: // \r to \n.
208: while ((nch = in.read(buff, 0, buff.length)) != -1) {
209: if (sbuff == null) {
210: sbuff = new StringBuffer(nch);
211: }
212: last = 0;
213: for (int counter = 0; counter < nch; counter++) {
214: switch (buff[counter]) {
215: case '\r':
216: if (lastWasCR) {
217: if (counter == 0) {
218: sbuff.append('\n');
219: } else {
220: buff[counter - 1] = '\n';
221: }
222: } else {
223: lastWasCR = true;
224: }
225: break;
226: case '\n':
227: if (lastWasCR) {
228: if (counter > (last + 1)) {
229: sbuff.append(buff, last, counter - last
230: - 1);
231: }
232: // else nothing to do, can skip \r, next write will
233: // write \n
234: lastWasCR = false;
235: last = counter;
236: }
237: break;
238: default:
239: if (lastWasCR) {
240: if (counter == 0) {
241: sbuff.append('\n');
242: } else {
243: buff[counter - 1] = '\n';
244: }
245: lastWasCR = false;
246: }
247: break;
248: }
249: }
250: if (last < nch) {
251: if (lastWasCR) {
252: if (last < (nch - 1)) {
253: sbuff.append(buff, last, nch - last - 1);
254: }
255: } else {
256: sbuff.append(buff, last, nch - last);
257: }
258: }
259: }
260: if (lastWasCR) {
261: sbuff.append('\n');
262: }
263: c.replaceSelection(sbuff != null ? sbuff.toString() : "");
264: }
265: }
266:
267: // --- TransferHandler methods ------------------------------------
268:
269: /**
270: * This is the type of transfer actions supported by the source. Some models are
271: * not mutable, so a transfer operation of COPY only should
272: * be advertised in that case.
273: *
274: * @param c The component holding the data to be transfered. This
275: * argument is provided to enable sharing of TransferHandlers by
276: * multiple components.
277: * @return This is implemented to return NONE if the component is a JPasswordField
278: * since exporting data via user gestures is not allowed. If the text component is
279: * editable, COPY_OR_MOVE is returned, otherwise just COPY is allowed.
280: */
281: public int getSourceActions(JComponent c) {
282: int actions = NONE;
283: if (!(c instanceof JPasswordField)) {
284: if (((JTextComponent) c).isEditable()) {
285: actions = COPY_OR_MOVE;
286: } else {
287: actions = COPY;
288: }
289: }
290: return actions;
291: }
292:
293: /**
294: * Create a Transferable to use as the source for a data transfer.
295: *
296: * @param comp The component holding the data to be transfered. This
297: * argument is provided to enable sharing of TransferHandlers by
298: * multiple components.
299: * @return The representation of the data to be transfered.
300: *
301: */
302: protected Transferable createTransferable(JComponent comp) {
303: exportComp = (JTextComponent) comp;
304: shouldRemove = true;
305: dontRemove = false;
306: p0 = exportComp.getSelectionStart();
307: p1 = exportComp.getSelectionEnd();
308: return (p0 != p1) ? (new TextTransferable(exportComp, p0, p1))
309: : null;
310: }
311:
312: /**
313: * This method is called after data has been exported. This
314: * method should remove the data that was transfered if the action
315: * was MOVE.
316: *
317: * @param source The component that was the source of the data.
318: * @param data The data that was transferred or possibly null
319: * if the action is <code>NONE</code>.
320: * @param action The actual action that was performed.
321: */
322: protected void exportDone(JComponent source, Transferable data,
323: int action) {
324: // only remove the text if shouldRemove has not been set to
325: // false by importData and only if the action is a move
326: if (shouldRemove && action == MOVE) {
327: TextTransferable t = (TextTransferable) data;
328: if (!dontRemove) {
329: t.removeText();
330: }
331: }
332:
333: exportComp = null;
334: }
335:
336: /**
337: * This method causes a transfer to a component from a clipboard or a
338: * DND drop operation. The Transferable represents the data to be
339: * imported into the component.
340: *
341: * @param comp The component to receive the transfer. This
342: * argument is provided to enable sharing of TransferHandlers by
343: * multiple components.
344: * @param t The data to import
345: * @return true if the data was inserted into the component, false otherwise.
346: */
347: public boolean importData(JComponent comp, Transferable t) {
348: JTextComponent c = (JTextComponent) comp;
349:
350: // if we are importing to the same component that we exported from
351: // then don't actually do anything if the drop location is inside
352: // the drag location and set shouldRemove to false so that exportDone
353: // knows not to remove any data
354: if (c == exportComp && c.getCaretPosition() >= p0
355: && c.getCaretPosition() <= p1) {
356: shouldRemove = false;
357: return true;
358: }
359:
360: boolean imported = false;
361: DataFlavor importFlavor = getImportFlavor(t
362: .getTransferDataFlavors(), c);
363: if (importFlavor != null) {
364: try {
365: boolean useRead = false;
366: if (comp instanceof JEditorPane) {
367: JEditorPane ep = (JEditorPane) comp;
368: if (!ep.getContentType().startsWith("text/plain")
369: && importFlavor.getMimeType().startsWith(
370: ep.getContentType())) {
371: useRead = true;
372:
373: }
374:
375: // XXX The hack, in order to call the callback (which in this case is
376: // expected to show the dialog letting user to deal with parametrized code clip at the drop time),
377: // masked into special flavor.
378: if (t
379: .isDataFlavorSupported(CodeClipTransferData.CODE_CLIP_DATA_FLAVOR)) {
380: try {
381: Runnable r = (Runnable) t
382: .getTransferData(CodeClipTransferData.CODE_CLIP_DATA_FLAVOR);
383: if (r != null) {
384: r.run();
385: }
386: } catch (IOException ioe) {
387: ErrorManager.getDefault().notify(
388: ErrorManager.INFORMATIONAL, ioe);
389: } catch (UnsupportedFlavorException ufe) {
390: ErrorManager.getDefault().notify(
391: ErrorManager.INFORMATIONAL, ufe);
392: }
393: }
394: }
395: Reader r = importFlavor.getReaderForText(t);
396: handleReaderImport(r, c, useRead);
397: imported = true;
398:
399: // #4946925 Trying to put the activation to the drop target.
400: TopComponent tc = (TopComponent) SwingUtilities
401: .getAncestorOfClass(TopComponent.class, c);
402: if (tc != null) {
403: tc.requestActive();
404: }
405: } catch (UnsupportedFlavorException ufe) {
406: ErrorManager.getDefault().notify(
407: ErrorManager.INFORMATIONAL, ufe);
408: } catch (BadLocationException ble) {
409: ErrorManager.getDefault().notify(
410: ErrorManager.INFORMATIONAL, ble);
411: } catch (IOException ioe) {
412: ErrorManager.getDefault().notify(
413: ErrorManager.INFORMATIONAL, ioe);
414: }
415: }
416: return imported;
417: }
418:
419: /**
420: * This method indicates if a component would accept an import of the given
421: * set of data flavors prior to actually attempting to import it.
422: *
423: * @param comp The component to receive the transfer. This
424: * argument is provided to enable sharing of TransferHandlers by
425: * multiple components.
426: * @param flavors The data formats available
427: * @return true if the data can be inserted into the component, false otherwise.
428: */
429: public boolean canImport(JComponent comp, DataFlavor[] flavors) {
430: JTextComponent c = (JTextComponent) comp;
431: if (!(c.isEditable() && c.isEnabled())) {
432: return false;
433: }
434: return (getImportFlavor(flavors, c) != null);
435: }
436:
437: /**
438: * A possible implementation of the Transferable interface
439: * for text components. For a JEditorPane with a rich set
440: * of EditorKit implementations, conversions could be made
441: * giving a wider set of formats. This is implemented to
442: * offer up only the active content type and text/plain
443: * (if that is not the active format) since that can be
444: * extracted from other formats.
445: */
446: static class TextTransferable implements Transferable, UIResource {
447:
448: // begin copied from BasicTransferable
449: protected String plainData = null;
450: protected String htmlData = null;
451:
452: private static DataFlavor[] htmlFlavors;
453: private static DataFlavor[] stringFlavors;
454: private static DataFlavor[] plainFlavors;
455:
456: static {
457: try {
458: htmlFlavors = new DataFlavor[3];
459: htmlFlavors[0] = new DataFlavor(
460: "text/html;class=java.lang.String");
461: htmlFlavors[1] = new DataFlavor(
462: "text/html;class=java.io.Reader");
463: htmlFlavors[2] = new DataFlavor(
464: "text/html;charset=unicode;class=java.io.InputStream");
465:
466: plainFlavors = new DataFlavor[3];
467: plainFlavors[0] = new DataFlavor(
468: "text/plain;class=java.lang.String");
469: plainFlavors[1] = new DataFlavor(
470: "text/plain;class=java.io.Reader");
471: plainFlavors[2] = new DataFlavor(
472: "text/plain;charset=unicode;class=java.io.InputStream");
473:
474: stringFlavors = new DataFlavor[2];
475: stringFlavors[0] = new DataFlavor(
476: DataFlavor.javaJVMLocalObjectMimeType
477: + ";class=java.lang.String");
478: stringFlavors[1] = DataFlavor.stringFlavor;
479:
480: } catch (ClassNotFoundException cle) {
481: System.err
482: .println("error initializing javax.swing.plaf.basic.BasicTranserable");
483: }
484: }
485:
486: /**
487: * Returns an array of DataFlavor objects indicating the flavors the data
488: * can be provided in. The array should be ordered according to preference
489: * for providing the data (from most richly descriptive to least descriptive).
490: * @return an array of data flavors in which this data can be transferred
491: */
492: public DataFlavor[] getTransferDataFlavors() {
493: DataFlavor[] richerFlavors = getRicherFlavors();
494: int nRicher = (richerFlavors != null) ? richerFlavors.length
495: : 0;
496: int nHTML = (isHTMLSupported()) ? htmlFlavors.length : 0;
497: int nPlain = (isPlainSupported()) ? plainFlavors.length : 0;
498: int nString = (isPlainSupported()) ? stringFlavors.length
499: : 0;
500: int nFlavors = nRicher + nHTML + nPlain + nString;
501: DataFlavor[] flavors = new DataFlavor[nFlavors];
502:
503: // fill in the array
504: int nDone = 0;
505: if (nRicher > 0) {
506: System.arraycopy(richerFlavors, 0, flavors, nDone,
507: nRicher);
508: nDone += nRicher;
509: }
510: if (nHTML > 0) {
511: System.arraycopy(htmlFlavors, 0, flavors, nDone, nHTML);
512: nDone += nHTML;
513: }
514: if (nPlain > 0) {
515: System.arraycopy(plainFlavors, 0, flavors, nDone,
516: nPlain);
517: nDone += nPlain;
518: }
519: if (nString > 0) {
520: System.arraycopy(stringFlavors, 0, flavors, nDone,
521: nString);
522: nDone += nString;
523: }
524: return flavors;
525: }
526:
527: /**
528: * Returns whether or not the specified data flavor is supported for
529: * this object.
530: * @param flavor the requested flavor for the data
531: * @return boolean indicating whether or not the data flavor is supported
532: */
533: public boolean isDataFlavorSupported(DataFlavor flavor) {
534: DataFlavor[] flavors = getTransferDataFlavors();
535: for (int i = 0; i < flavors.length; i++) {
536: if (flavors[i].equals(flavor)) {
537: return true;
538: }
539: }
540: return false;
541: }
542:
543: /**
544: * Returns an object which represents the data to be transferred. The class
545: * of the object returned is defined by the representation class of the flavor.
546: *
547: * @param flavor the requested flavor for the data
548: * @see DataFlavor#getRepresentationClass
549: * @exception IOException if the data is no longer available
550: * in the requested flavor.
551: * @exception UnsupportedFlavorException if the requested data flavor is
552: * not supported.
553: */
554: public Object getTransferData(DataFlavor flavor)
555: throws UnsupportedFlavorException, IOException {
556: DataFlavor[] richerFlavors = getRicherFlavors();
557: if (isRicherFlavor(flavor)) {
558: return getRicherData(flavor);
559: } else if (isHTMLFlavor(flavor)) {
560: String data = getHTMLData();
561: data = (data == null) ? "" : data;
562: if (String.class
563: .equals(flavor.getRepresentationClass())) {
564: return data;
565: } else if (Reader.class.equals(flavor
566: .getRepresentationClass())) {
567: return new StringReader(data);
568: } else if (InputStream.class.equals(flavor
569: .getRepresentationClass())) {
570: return new StringBufferInputStream(data);
571: }
572: // fall through to unsupported
573: } else if (isPlainFlavor(flavor)) {
574: String data = getPlainData();
575: data = (data == null) ? "" : data;
576: if (String.class
577: .equals(flavor.getRepresentationClass())) {
578: return data;
579: } else if (Reader.class.equals(flavor
580: .getRepresentationClass())) {
581: return new StringReader(data);
582: } else if (InputStream.class.equals(flavor
583: .getRepresentationClass())) {
584: return new StringBufferInputStream(data);
585: }
586: // fall through to unsupported
587:
588: } else if (isStringFlavor(flavor)) {
589: String data = getPlainData();
590: data = (data == null) ? "" : data;
591: return data;
592: }
593: throw new UnsupportedFlavorException(flavor);
594: }
595:
596: // --- richer subclass flavors ----------------------------------------------
597:
598: protected boolean isRicherFlavor(DataFlavor flavor) {
599: DataFlavor[] richerFlavors = getRicherFlavors();
600: int nFlavors = (richerFlavors != null) ? richerFlavors.length
601: : 0;
602: for (int i = 0; i < nFlavors; i++) {
603: if (richerFlavors[i].equals(flavor)) {
604: return true;
605: }
606: }
607: return false;
608: }
609:
610: // --- html flavors ----------------------------------------------------------
611:
612: /**
613: * Returns whether or not the specified data flavor is an HTML flavor that
614: * is supported.
615: * @param flavor the requested flavor for the data
616: * @return boolean indicating whether or not the data flavor is supported
617: */
618: protected boolean isHTMLFlavor(DataFlavor flavor) {
619: DataFlavor[] flavors = htmlFlavors;
620: for (int i = 0; i < flavors.length; i++) {
621: if (flavors[i].equals(flavor)) {
622: return true;
623: }
624: }
625: return false;
626: }
627:
628: /**
629: * Should the HTML flavors be offered? If so, the method
630: * getHTMLData should be implemented to provide something reasonable.
631: */
632: protected boolean isHTMLSupported() {
633: return htmlData != null;
634: }
635:
636: /**
637: * Fetch the data in a text/html format
638: */
639: protected String getHTMLData() {
640: return htmlData;
641: }
642:
643: // --- plain text flavors ----------------------------------------------------
644:
645: /**
646: * Returns whether or not the specified data flavor is an plain flavor that
647: * is supported.
648: * @param flavor the requested flavor for the data
649: * @return boolean indicating whether or not the data flavor is supported
650: */
651: protected boolean isPlainFlavor(DataFlavor flavor) {
652: DataFlavor[] flavors = plainFlavors;
653: for (int i = 0; i < flavors.length; i++) {
654: if (flavors[i].equals(flavor)) {
655: return true;
656: }
657: }
658: return false;
659: }
660:
661: /**
662: * Should the plain text flavors be offered? If so, the method
663: * getPlainData should be implemented to provide something reasonable.
664: */
665: protected boolean isPlainSupported() {
666: return plainData != null;
667: }
668:
669: /**
670: * Fetch the data in a text/plain format.
671: */
672: protected String getPlainData() {
673: return plainData;
674: }
675:
676: // --- string flavorss --------------------------------------------------------
677:
678: /**
679: * Returns whether or not the specified data flavor is a String flavor that
680: * is supported.
681: * @param flavor the requested flavor for the data
682: * @return boolean indicating whether or not the data flavor is supported
683: */
684: protected boolean isStringFlavor(DataFlavor flavor) {
685: DataFlavor[] flavors = stringFlavors;
686: for (int i = 0; i < flavors.length; i++) {
687: if (flavors[i].equals(flavor)) {
688: return true;
689: }
690: }
691: return false;
692: }
693:
694: // end copied from BasicTransferable
695:
696: TextTransferable(JTextComponent c, int start, int end) {
697: this .c = c;
698:
699: Document doc = c.getDocument();
700:
701: try {
702: p0 = doc.createPosition(start);
703: p1 = doc.createPosition(end);
704:
705: plainData = c.getSelectedText();
706:
707: if (c instanceof JEditorPane) {
708: JEditorPane ep = (JEditorPane) c;
709:
710: mimeType = ep.getContentType();
711:
712: if (mimeType.startsWith("text/plain")) {
713: return;
714: }
715:
716: StringWriter sw = new StringWriter(p1.getOffset()
717: - p0.getOffset());
718: ep.getEditorKit().write(sw, doc, p0.getOffset(),
719: p1.getOffset() - p0.getOffset());
720:
721: if (mimeType.startsWith("text/html")) {
722: htmlData = sw.toString();
723: } else {
724: richText = sw.toString();
725: }
726: }
727: } catch (BadLocationException ble) {
728: } catch (IOException ioe) {
729: }
730: }
731:
732: void removeText() {
733: if ((p0 != null) && (p1 != null)
734: && (p0.getOffset() != p1.getOffset())) {
735: try {
736: Document doc = c.getDocument();
737: doc.remove(p0.getOffset(), p1.getOffset()
738: - p0.getOffset());
739: } catch (BadLocationException e) {
740: }
741: }
742: }
743:
744: // ---- EditorKit other than plain or HTML text -----------------------
745:
746: /**
747: * If the EditorKit is not for text/plain or text/html, that format
748: * is supported through the "richer flavors" part of BasicTransferable.
749: */
750: protected DataFlavor[] getRicherFlavors() {
751: if (richText == null) {
752: return null;
753: }
754:
755: try {
756: DataFlavor[] flavors = new DataFlavor[3];
757: flavors[0] = new DataFlavor(mimeType
758: + ";class=java.lang.String");
759: flavors[1] = new DataFlavor(mimeType
760: + ";class=java.io.Reader");
761: flavors[2] = new DataFlavor(mimeType
762: + ";class=java.io.InputStream;charset=unicode");
763: return flavors;
764: } catch (ClassNotFoundException cle) {
765: // fall through to unsupported (should not happen)
766: }
767:
768: return null;
769: }
770:
771: /**
772: * The only richer format supported is the file list flavor
773: */
774: protected Object getRicherData(DataFlavor flavor)
775: throws UnsupportedFlavorException {
776: if (richText == null) {
777: return null;
778: }
779:
780: if (String.class.equals(flavor.getRepresentationClass())) {
781: return richText;
782: } else if (Reader.class.equals(flavor
783: .getRepresentationClass())) {
784: return new StringReader(richText);
785: } else if (InputStream.class.equals(flavor
786: .getRepresentationClass())) {
787: return new StringBufferInputStream(richText);
788: }
789: throw new UnsupportedFlavorException(flavor);
790: }
791:
792: Position p0;
793: Position p1;
794: String mimeType;
795: String richText;
796: JTextComponent c;
797: }
798:
799: // XXX Hack to enable poping up a dialog when DnD of code clip.
800: public static class CodeClipTransferData extends StringSelection {
801:
802: // XXX Fake DataFlavor.. for cheating to retrieve the callback.
803: private static final DataFlavor CODE_CLIP_DATA_FLAVOR = new DataFlavor(
804: CodeClipTransferData.class, CodeClipTransferData.class
805: .getName()); // TEMP
806:
807: // XXX Callback to provide the popup.
808: private Runnable callback;
809: // XXX We need to manipulate the data in this class (the superclass is private).
810: private String data;
811:
812: public CodeClipTransferData(String data) {
813: super (data); // Just fake.
814: this .data = data;
815: }
816:
817: public void setCallback(Runnable callback) {
818: this .callback = callback;
819: }
820:
821: public void resetData(String data) {
822: this .data = data;
823: }
824:
825: // XXX Overriden to manipulate with our data field.
826: public DataFlavor[] getTransferDataFlavors() {
827: DataFlavor[] dfs = super .getTransferDataFlavors();
828: List flavors = new ArrayList(Arrays.asList(dfs));
829: flavors.add(CODE_CLIP_DATA_FLAVOR);
830: return (DataFlavor[]) flavors.toArray(new DataFlavor[0]);
831: }
832:
833: // XXX Overriden to manipulate with our data field.
834: public boolean isDataFlavorSupported(DataFlavor flavor) {
835: if (flavor == CODE_CLIP_DATA_FLAVOR) {
836: return true;
837: }
838:
839: return super .isDataFlavorSupported(flavor);
840: }
841:
842: // XXX Overriden to manipulate with our data field and provide the hacking callback.
843: public Object getTransferData(DataFlavor flavor)
844: throws UnsupportedFlavorException, IOException {
845: if (flavor == CODE_CLIP_DATA_FLAVOR) {
846: return callback;
847: }
848:
849: // JCK Test StringSelection0007: if 'flavor' is null, throw NPE
850: if (flavor.equals(DataFlavor.stringFlavor)) {
851: return (Object) data;
852: } else if (flavor.equals(DataFlavor.plainTextFlavor)) { // deprecated
853: return new StringReader(data);
854: } else {
855: throw new UnsupportedFlavorException(flavor);
856: }
857: }
858:
859: }
860: }
|