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-2006 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.editor.html;
0042:
0043: import java.awt.datatransfer.DataFlavor;
0044: import java.awt.datatransfer.Transferable;
0045: import java.awt.datatransfer.UnsupportedFlavorException;
0046: import java.awt.event.ActionEvent;
0047: import java.awt.im.InputContext;
0048: import java.io.ByteArrayInputStream;
0049: import java.io.IOException;
0050: import java.io.InputStream;
0051: import java.io.Reader;
0052: import java.io.StringReader;
0053: import java.io.StringWriter;
0054: import java.util.List;
0055: import java.util.Set;
0056: import java.util.logging.Level;
0057: import java.util.logging.Logger;
0058: import javax.swing.Action;
0059: import javax.swing.JComponent;
0060: import javax.swing.JEditorPane;
0061: import javax.swing.JPasswordField;
0062: import javax.swing.TransferHandler;
0063: import javax.swing.plaf.UIResource;
0064: import javax.swing.text.BadLocationException;
0065: import javax.swing.text.Caret;
0066: import javax.swing.text.Document;
0067: import javax.swing.text.EditorKit;
0068: import javax.swing.text.Position;
0069: import javax.swing.text.TextAction;
0070: import javax.swing.text.JTextComponent;
0071: import org.netbeans.api.html.lexer.HTMLTokenId;
0072: import org.netbeans.api.lexer.LanguagePath;
0073: import org.netbeans.api.lexer.TokenHierarchy;
0074: import org.netbeans.editor.*;
0075: import org.netbeans.editor.BaseKit.DeleteCharAction;
0076: import org.netbeans.editor.ext.ExtKit.ExtDefaultKeyTypedAction;
0077: import org.netbeans.editor.ext.html.*;
0078: import org.netbeans.editor.ext.html.parser.SyntaxParser;
0079: import org.netbeans.modules.gsf.api.BracketCompletion;
0080: import org.netbeans.modules.editor.NbEditorKit;
0081: import org.netbeans.modules.editor.gsfret.InstantRenameAction;
0082: import org.netbeans.modules.editor.indent.api.Reformat;
0083: import org.netbeans.modules.gsf.Language;
0084: import org.netbeans.modules.gsf.LanguageRegistry;
0085: import org.netbeans.modules.gsf.SelectCodeElementAction;
0086: import org.netbeans.modules.html.editor.coloring.EmbeddingUpdater;
0087: import org.openide.util.Exceptions;
0088:
0089: /**
0090: * Editor kit implementation for HTML content type
0091: *
0092: * @author Miloslav Metelka
0093: * @version 1.00
0094: */
0095: public class HTMLKit extends NbEditorKit implements
0096: org.openide.util.HelpCtx.Provider {
0097:
0098: public org.openide.util.HelpCtx getHelpCtx() {
0099: return new org.openide.util.HelpCtx(HTMLKit.class);
0100: }
0101:
0102: private static final Logger LOGGER = Logger.getLogger(HTMLKit.class
0103: .getName());
0104: static final long serialVersionUID = -1381945567613910297L;
0105: public static final String HTML_MIME_TYPE = "text/html"; // NOI18N
0106: public static final String shiftInsertBreakAction = "shift-insert-break"; // NOI18N
0107: private static boolean setupReadersInitialized = false;
0108:
0109: public HTMLKit() {
0110: this (HTML_MIME_TYPE);
0111: }
0112:
0113: public HTMLKit(String mimeType) {
0114: super ();
0115: if (!setupReadersInitialized) {
0116: NbReaderProvider.setupReaders();
0117: setupReadersInitialized = true;
0118: }
0119: }
0120:
0121: @Override
0122: public String getContentType() {
0123: return HTML_MIME_TYPE;
0124: }
0125:
0126: public Object clone() {
0127: return new HTMLKit();
0128: }
0129:
0130: protected void initDocument(final BaseDocument doc) {
0131: TokenHierarchy hi = TokenHierarchy.get(doc);
0132: if (hi == null) {
0133: LOGGER.log(Level.WARNING,
0134: "TokenHierarchy is null for document " + doc);
0135: return;
0136: }
0137:
0138: //listen on the HTML parser and recolor after changes
0139: LanguagePath htmlLP = LanguagePath.get(HTMLTokenId.language());
0140: SyntaxParser.get(doc, htmlLP).addSyntaxParserListener(
0141: new EmbeddingUpdater(doc));
0142: }
0143:
0144: /** Called after the kit is installed into JEditorPane */
0145: public void install(javax.swing.JEditorPane c) {
0146: super .install(c);
0147: c.setTransferHandler(new HTMLTransferHandler());
0148: }
0149:
0150: protected Action[] createActions() {
0151: Action[] HTMLActions = new Action[] {
0152: new HTMLInsertBreakAction(),
0153: new HTMLDefaultKeyTypedAction(),
0154: new HTMLDeleteCharAction(deletePrevCharAction, false),
0155: new HTMLDeleteCharAction(deleteNextCharAction, true),
0156: new SelectCodeElementAction(
0157: SelectCodeElementAction.selectNextElementAction,
0158: true),
0159: new SelectCodeElementAction(
0160: SelectCodeElementAction.selectPreviousElementAction,
0161: false), new InstantRenameAction(), };
0162: return TextAction.augmentList(super .createActions(),
0163: HTMLActions);
0164: }
0165:
0166: static BracketCompletion getBracketCompletion(Document doc,
0167: int offset) {
0168: BaseDocument baseDoc = (BaseDocument) doc;
0169: List<Language> list = LanguageRegistry.getInstance()
0170: .getEmbeddedLanguages(baseDoc, offset);
0171: for (Language l : list) {
0172: if (l.getBracketCompletion() != null) {
0173: return l.getBracketCompletion();
0174: }
0175: }
0176:
0177: return null;
0178: }
0179:
0180: /**
0181: * Returns true if bracket completion is enabled in options.
0182: */
0183: private static boolean completionSettingEnabled() {
0184: //return ((Boolean)Settings.getValue(HTMLEditorKit.class, JavaSettingsNames.PAIR_CHARACTERS_COMPLETION)).booleanValue();
0185: return true;
0186: }
0187:
0188: public class HTMLInsertBreakAction extends InsertBreakAction {
0189:
0190: static final long serialVersionUID = -1506173310438326380L;
0191:
0192: @Override
0193: protected Object beforeBreak(JTextComponent target,
0194: BaseDocument doc, Caret caret) {
0195: if (completionSettingEnabled()) {
0196: BracketCompletion bracketCompletion = getBracketCompletion(
0197: doc, caret.getDot());
0198:
0199: if (bracketCompletion != null) {
0200: try {
0201: int newOffset = bracketCompletion.beforeBreak(
0202: doc, caret.getDot(), target);
0203:
0204: if (newOffset >= 0) {
0205: return new Integer(newOffset);
0206: }
0207: } catch (BadLocationException ble) {
0208: Exceptions.printStackTrace(ble);
0209: }
0210: }
0211: }
0212:
0213: // return Boolean.TRUE;
0214: return null;
0215: }
0216:
0217: @Override
0218: protected void afterBreak(JTextComponent target,
0219: BaseDocument doc, Caret caret, Object cookie) {
0220: if (completionSettingEnabled()) {
0221: if (cookie != null) {
0222: if (cookie instanceof Integer) {
0223: // integer
0224: int dotPos = ((Integer) cookie).intValue();
0225: if (dotPos != -1) {
0226: caret.setDot(dotPos);
0227: } else {
0228: int nowDotPos = caret.getDot();
0229: caret.setDot(nowDotPos + 1);
0230: }
0231: }
0232: }
0233: }
0234: }
0235: }
0236:
0237: public static class HTMLDefaultKeyTypedAction extends
0238: ExtDefaultKeyTypedAction {
0239:
0240: private JTextComponent currentTarget;
0241:
0242: public void actionPerformed(ActionEvent evt,
0243: JTextComponent target) {
0244: currentTarget = target;
0245: super .actionPerformed(evt, target);
0246: currentTarget = null;
0247: }
0248:
0249: @Override
0250: protected void insertString(BaseDocument doc, int dotPos,
0251: Caret caret, String str, boolean overwrite)
0252: throws BadLocationException {
0253:
0254: if (completionSettingEnabled()) {
0255: BracketCompletion bracketCompletion = getBracketCompletion(
0256: doc, dotPos);
0257:
0258: if (bracketCompletion != null) {
0259: // TODO - check if we're in a comment etc. and if so, do nothing
0260: boolean handled = bracketCompletion
0261: .beforeCharInserted(doc, dotPos,
0262: currentTarget, str.charAt(0));
0263:
0264: if (!handled) {
0265: super .insertString(doc, dotPos, caret, str,
0266: overwrite);
0267: handled = bracketCompletion.afterCharInserted(
0268: doc, dotPos, currentTarget, str
0269: .charAt(0));
0270: }
0271:
0272: return;
0273: }
0274: }
0275:
0276: super .insertString(doc, dotPos, caret, str, overwrite);
0277: HTMLAutoCompletion.charInserted(doc, dotPos, caret, str
0278: .charAt(0));
0279: handleTagClosingSymbol(doc, dotPos, str.charAt(0));
0280: }
0281:
0282: @Override
0283: protected void replaceSelection(JTextComponent target,
0284: int dotPos, Caret caret, String str, boolean overwrite)
0285: throws BadLocationException {
0286: char insertedChar = str.charAt(0);
0287: Document document = target.getDocument();
0288:
0289: if (document instanceof BaseDocument) {
0290: BaseDocument doc = (BaseDocument) document;
0291:
0292: if (completionSettingEnabled()) {
0293: BracketCompletion bracketCompletion = getBracketCompletion(
0294: doc, dotPos);
0295:
0296: if (bracketCompletion != null) {
0297: try {
0298: int caretPosition = caret.getDot();
0299:
0300: boolean handled = bracketCompletion
0301: .beforeCharInserted(doc,
0302: caretPosition, target,
0303: insertedChar);
0304:
0305: int p0 = Math.min(caret.getDot(), caret
0306: .getMark());
0307: int p1 = Math.max(caret.getDot(), caret
0308: .getMark());
0309:
0310: if (p0 != p1) {
0311: doc.remove(p0, p1 - p0);
0312: }
0313:
0314: if (!handled) {
0315: if ((str != null) && (str.length() > 0)) {
0316: doc.insertString(p0, str, null);
0317: }
0318:
0319: bracketCompletion.afterCharInserted(
0320: doc, caret.getDot() - 1,
0321: target, insertedChar);
0322: }
0323: } catch (BadLocationException e) {
0324: e.printStackTrace();
0325: }
0326:
0327: return;
0328: }
0329: }
0330: }
0331:
0332: super .replaceSelection(target, dotPos, caret, str,
0333: overwrite);
0334: }
0335:
0336: private void handleTagClosingSymbol(BaseDocument doc,
0337: int dotPos, char lastChar) throws BadLocationException {
0338: if (lastChar == '>') {
0339: TokenHierarchy tokenHierarchy = TokenHierarchy.get(doc);
0340: for (LanguagePath languagePath : (Set<LanguagePath>) tokenHierarchy
0341: .languagePaths()) {
0342: if (languagePath.innerLanguage() == HTMLTokenId
0343: .language()) {
0344: HTMLLexerFormatter htmlFormatter = new HTMLLexerFormatter(
0345: languagePath);
0346:
0347: if (htmlFormatter.isJustAfterClosingTag(doc,
0348: dotPos)) {
0349: Reformat reformat = Reformat.get(doc);
0350: reformat.lock();
0351:
0352: try {
0353: doc.atomicLock();
0354: try {
0355: int startOffset = Utilities
0356: .getRowStart(doc, dotPos);
0357: int endOffset = Utilities
0358: .getRowEnd(doc, dotPos);
0359: reformat.reformat(startOffset,
0360: endOffset);
0361: } finally {
0362: doc.atomicUnlock();
0363: }
0364: } finally {
0365: reformat.unlock();
0366: }
0367: }
0368: }
0369: }
0370: }
0371: }
0372: }
0373:
0374: public static class HTMLDeleteCharAction extends DeleteCharAction {
0375:
0376: private JTextComponent currentTarget;
0377:
0378: public HTMLDeleteCharAction(String name, boolean nextChar) {
0379: super (name, nextChar);
0380: }
0381:
0382: @Override
0383: public void actionPerformed(ActionEvent evt,
0384: JTextComponent target) {
0385: currentTarget = target;
0386: super .actionPerformed(evt, target);
0387: currentTarget = null;
0388: }
0389:
0390: protected void charBackspaced(BaseDocument doc, int dotPos,
0391: Caret caret, char ch) throws BadLocationException {
0392: if (completionSettingEnabled()) {
0393: BracketCompletion bracketCompletion = getBracketCompletion(
0394: doc, dotPos);
0395:
0396: if (bracketCompletion != null) {
0397: boolean success = bracketCompletion.charBackspaced(
0398: doc, dotPos, currentTarget, ch);
0399: return;
0400: }
0401: }
0402:
0403: super .charBackspaced(doc, dotPos, caret, ch);
0404: HTMLAutoCompletion.charDeleted(doc, dotPos, caret, ch);
0405: }
0406: }
0407:
0408: /* !!!!!!!!!!!!!!!!!!!!!
0409: *
0410: * Inner classes bellow were taken from BasicTextUI and rewritten in the place marked
0411: * with [REWRITE_PLACE]. This needs to be done to fix the issue #43309
0412: *
0413: * !!!!!!!!!!!!!!!!!!!!!
0414: */
0415: static class HTMLTransferHandler extends TransferHandler implements
0416: UIResource {
0417:
0418: private JTextComponent exportComp;
0419: private boolean shouldRemove;
0420: private int p0;
0421: private int p1;
0422:
0423: /**
0424: * Try to find a flavor that can be used to import a Transferable.
0425: * The set of usable flavors are tried in the following order:
0426: * <ol>
0427: * <li>First, an attempt is made to find a flavor matching the content type
0428: * of the EditorKit for the component.
0429: * <li>Second, an attempt to find a text/plain flavor is made.
0430: * <li>Third, an attempt to find a flavor representing a String reference
0431: * in the same VM is made.
0432: * <li>Lastly, DataFlavor.stringFlavor is searched for.
0433: * </ol>
0434: */
0435: protected DataFlavor getImportFlavor(DataFlavor[] flavors,
0436: JTextComponent c) {
0437: DataFlavor plainFlavor = null;
0438: DataFlavor refFlavor = null;
0439: DataFlavor stringFlavor = null;
0440:
0441: if (c instanceof JEditorPane) {
0442: for (int i = 0; i < flavors.length; i++) {
0443: String mime = flavors[i].getMimeType();
0444: if (mime.startsWith(((JEditorPane) c)
0445: .getEditorKit().getContentType())) {
0446: //return flavors[i]; [REWRITE_PLACE]
0447: } else if (plainFlavor == null
0448: && mime.startsWith("text/plain")) { //NOI18N
0449: plainFlavor = flavors[i];
0450: } else if (refFlavor == null
0451: && mime
0452: .startsWith("application/x-java-jvm-local-objectref") //NOI18N
0453: && flavors[i].getRepresentationClass() == java.lang.String.class) {
0454: refFlavor = flavors[i];
0455: } else if (stringFlavor == null
0456: && flavors[i]
0457: .equals(DataFlavor.stringFlavor)) {
0458: stringFlavor = flavors[i];
0459: }
0460: }
0461: if (plainFlavor != null) {
0462: return plainFlavor;
0463: } else if (refFlavor != null) {
0464: return refFlavor;
0465: } else if (stringFlavor != null) {
0466: return stringFlavor;
0467: }
0468: return null;
0469: }
0470:
0471: for (int i = 0; i < flavors.length; i++) {
0472: String mime = flavors[i].getMimeType();
0473: if (mime.startsWith("text/plain")) { //NOI18N
0474: return flavors[i];
0475: } else if (refFlavor == null
0476: && mime
0477: .startsWith("application/x-java-jvm-local-objectref") //NOI18N
0478: && flavors[i].getRepresentationClass() == java.lang.String.class) {
0479: refFlavor = flavors[i];
0480: } else if (stringFlavor == null
0481: && flavors[i].equals(DataFlavor.stringFlavor)) {
0482: stringFlavor = flavors[i];
0483: }
0484: }
0485: if (refFlavor != null) {
0486: return refFlavor;
0487: } else if (stringFlavor != null) {
0488: return stringFlavor;
0489: }
0490: return null;
0491: }
0492:
0493: /**
0494: * Import the given stream data into the text component.
0495: */
0496: protected void handleReaderImport(Reader in, JTextComponent c,
0497: boolean useRead) throws BadLocationException,
0498: IOException {
0499: if (useRead) {
0500: int startPosition = c.getSelectionStart();
0501: int endPosition = c.getSelectionEnd();
0502: int length = endPosition - startPosition;
0503: EditorKit kit = c.getUI().getEditorKit(c);
0504: Document doc = c.getDocument();
0505: if (length > 0) {
0506: doc.remove(startPosition, length);
0507: }
0508: kit.read(in, doc, startPosition);
0509: } else {
0510: char[] buff = new char[1024];
0511: int nch;
0512: boolean lastWasCR = false;
0513: int last;
0514: StringBuffer sbuff = null;
0515:
0516: // Read in a block at a time, mapping \r\n to \n, as well as single
0517: // \r to \n.
0518: while ((nch = in.read(buff, 0, buff.length)) != -1) {
0519: if (sbuff == null) {
0520: sbuff = new StringBuffer(nch);
0521: }
0522: last = 0;
0523: for (int counter = 0; counter < nch; counter++) {
0524: switch (buff[counter]) {
0525: case '\r':
0526: if (lastWasCR) {
0527: if (counter == 0) {
0528: sbuff.append('\n');
0529: } else {
0530: buff[counter - 1] = '\n';
0531: }
0532: } else {
0533: lastWasCR = true;
0534: }
0535: break;
0536: case '\n':
0537: if (lastWasCR) {
0538: if (counter > (last + 1)) {
0539: sbuff.append(buff, last, counter
0540: - last - 1);
0541: }
0542: // else nothing to do, can skip \r, next write will
0543: // write \n
0544: lastWasCR = false;
0545: last = counter;
0546: }
0547: break;
0548: default:
0549: if (lastWasCR) {
0550: if (counter == 0) {
0551: sbuff.append('\n');
0552: } else {
0553: buff[counter - 1] = '\n';
0554: }
0555: lastWasCR = false;
0556: }
0557: break;
0558: }
0559: }
0560: if (last < nch) {
0561: if (lastWasCR) {
0562: if (last < (nch - 1)) {
0563: sbuff
0564: .append(buff, last, nch - last
0565: - 1);
0566: }
0567: } else {
0568: sbuff.append(buff, last, nch - last);
0569: }
0570: }
0571: }
0572: if (lastWasCR) {
0573: sbuff.append('\n');
0574: }
0575: c.replaceSelection(sbuff != null ? sbuff.toString()
0576: : ""); //NOI18N
0577: }
0578: }
0579:
0580: // --- TransferHandler methods ------------------------------------
0581: /**
0582: * This is the type of transfer actions supported by the source. Some models are
0583: * not mutable, so a transfer operation of COPY only should
0584: * be advertised in that case.
0585: *
0586: * @param c The component holding the data to be transfered. This
0587: * argument is provided to enable sharing of TransferHandlers by
0588: * multiple components.
0589: * @return This is implemented to return NONE if the component is a JPasswordField
0590: * since exporting data via user gestures is not allowed. If the text component is
0591: * editable, COPY_OR_MOVE is returned, otherwise just COPY is allowed.
0592: */
0593: public int getSourceActions(JComponent c) {
0594: int actions = NONE;
0595: if (!(c instanceof JPasswordField)) {
0596: if (((JTextComponent) c).isEditable()) {
0597: actions = COPY_OR_MOVE;
0598: } else {
0599: actions = COPY;
0600: }
0601: }
0602: return actions;
0603: }
0604:
0605: /**
0606: * Create a Transferable to use as the source for a data transfer.
0607: *
0608: * @param comp The component holding the data to be transfered. This
0609: * argument is provided to enable sharing of TransferHandlers by
0610: * multiple components.
0611: * @return The representation of the data to be transfered.
0612: *
0613: */
0614: protected Transferable createTransferable(JComponent comp) {
0615: exportComp = (JTextComponent) comp;
0616: shouldRemove = true;
0617: p0 = exportComp.getSelectionStart();
0618: p1 = exportComp.getSelectionEnd();
0619: return (p0 != p1) ? (new HTMLTransferable(exportComp, p0,
0620: p1)) : null;
0621: }
0622:
0623: /**
0624: * This method is called after data has been exported. This method should remove
0625: * the data that was transfered if the action was MOVE.
0626: *
0627: * @param source The component that was the source of the data.
0628: * @param data The data that was transferred or possibly null
0629: * if the action is <code>NONE</code>.
0630: * @param action The actual action that was performed.
0631: */
0632: protected void exportDone(JComponent source, Transferable data,
0633: int action) {
0634: // only remove the text if shouldRemove has not been set to
0635: // false by importData and only if the action is a move
0636: if (shouldRemove && action == MOVE) {
0637: HTMLTransferable t = (HTMLTransferable) data;
0638: t.removeText();
0639: }
0640:
0641: exportComp = null;
0642: }
0643:
0644: /**
0645: * This method causes a transfer to a component from a clipboard or a
0646: * DND drop operation. The Transferable represents the data to be
0647: * imported into the component.
0648: *
0649: * @param comp The component to receive the transfer. This
0650: * argument is provided to enable sharing of TransferHandlers by
0651: * multiple components.
0652: * @param t The data to import
0653: * @return true if the data was inserted into the component, false otherwise.
0654: */
0655: public boolean importData(JComponent comp, Transferable t) {
0656: JTextComponent c = (JTextComponent) comp;
0657:
0658: // if we are importing to the same component that we exported from
0659: // then don't actually do anything if the drop location is inside
0660: // the drag location and set shouldRemove to false so that exportDone
0661: // knows not to remove any data
0662: if (c == exportComp && c.getCaretPosition() >= p0
0663: && c.getCaretPosition() <= p1) {
0664: shouldRemove = false;
0665: return true;
0666: }
0667:
0668: boolean imported = false;
0669: DataFlavor importFlavor = getImportFlavor(t
0670: .getTransferDataFlavors(), c);
0671: if (importFlavor != null) {
0672: try {
0673: boolean useRead = false;
0674: if (comp instanceof JEditorPane) {
0675: JEditorPane ep = (JEditorPane) comp;
0676: if (!ep.getContentType().startsWith(
0677: "text/plain")
0678: && //NOI18N
0679: importFlavor.getMimeType().startsWith(
0680: ep.getContentType())) {
0681: useRead = true;
0682: }
0683: }
0684: InputContext ic = c.getInputContext();
0685: if (ic != null) {
0686: ic.endComposition();
0687: }
0688: Reader r = importFlavor.getReaderForText(t);
0689: handleReaderImport(r, c, useRead);
0690: imported = true;
0691: } catch (UnsupportedFlavorException ufe) {
0692: //just ignore
0693: } catch (BadLocationException ble) {
0694: //just ignore
0695: } catch (IOException ioe) {
0696: //just ignore
0697: }
0698: }
0699: return imported;
0700: }
0701:
0702: /**
0703: * This method indicates if a component would accept an import of the given
0704: * set of data flavors prior to actually attempting to import it.
0705: *
0706: * @param comp The component to receive the transfer. This
0707: * argument is provided to enable sharing of TransferHandlers by
0708: * multiple components.
0709: * @param flavors The data formats available
0710: * @return true if the data can be inserted into the component, false otherwise.
0711: */
0712: public boolean canImport(JComponent comp, DataFlavor[] flavors) {
0713: JTextComponent c = (JTextComponent) comp;
0714: if (!(c.isEditable() && c.isEnabled())) {
0715: return false;
0716: }
0717: return (getImportFlavor(flavors, c) != null);
0718: }
0719:
0720: /**
0721: * A possible implementation of the Transferable interface
0722: * for text components. For a JEditorPane with a rich set
0723: * of EditorKit implementations, conversions could be made
0724: * giving a wider set of formats. This is implemented to
0725: * offer up only the active content type and text/plain
0726: * (if that is not the active format) since that can be
0727: * extracted from other formats.
0728: */
0729: static class HTMLTransferable extends BasicTransferable {
0730:
0731: HTMLTransferable(JTextComponent c, int start, int end) {
0732: super (null, null);
0733:
0734: this .c = c;
0735:
0736: Document doc = c.getDocument();
0737:
0738: try {
0739: p0 = doc.createPosition(start);
0740: p1 = doc.createPosition(end);
0741:
0742: plainData = c.getSelectedText();
0743:
0744: if (c instanceof JEditorPane) {
0745: JEditorPane ep = (JEditorPane) c;
0746:
0747: mimeType = ep.getContentType();
0748:
0749: if (mimeType.startsWith("text/plain")) { //NOI18N
0750: return;
0751: }
0752:
0753: StringWriter sw = new StringWriter(p1
0754: .getOffset()
0755: - p0.getOffset());
0756: ep.getEditorKit().write(sw, doc,
0757: p0.getOffset(),
0758: p1.getOffset() - p0.getOffset());
0759:
0760: if (mimeType.startsWith("text/html")) { //NOI18N
0761: htmlData = sw.toString();
0762: } else {
0763: richText = sw.toString();
0764: }
0765: }
0766: } catch (BadLocationException ble) {
0767: } catch (IOException ioe) {
0768: }
0769: }
0770:
0771: void removeText() {
0772: if ((p0 != null) && (p1 != null)
0773: && (p0.getOffset() != p1.getOffset())) {
0774: try {
0775: Document doc = c.getDocument();
0776: doc.remove(p0.getOffset(), p1.getOffset()
0777: - p0.getOffset());
0778: } catch (BadLocationException e) {
0779: }
0780: }
0781: }
0782:
0783: // ---- EditorKit other than plain or HTML text -----------------------
0784: /**
0785: * If the EditorKit is not for text/plain or text/html, that format
0786: * is supported through the "richer flavors" part of BasicTransferable.
0787: */
0788: protected DataFlavor[] getRicherFlavors() {
0789: if (richText == null) {
0790: return null;
0791: }
0792:
0793: try {
0794: DataFlavor[] flavors = new DataFlavor[3];
0795: flavors[0] = new DataFlavor(mimeType
0796: + ";class=java.lang.String"); //NOI18N
0797: flavors[1] = new DataFlavor(mimeType
0798: + ";class=java.io.Reader"); //NOI18N
0799: flavors[2] = new DataFlavor(
0800: mimeType
0801: + ";class=java.io.InputStream;charset=unicode"); //NOI18N
0802: return flavors;
0803: } catch (ClassNotFoundException cle) {
0804: // fall through to unsupported (should not happen)
0805: }
0806:
0807: return null;
0808: }
0809:
0810: /**
0811: * The only richer format supported is the file list flavor
0812: */
0813: protected Object getRicherData(DataFlavor flavor)
0814: throws UnsupportedFlavorException {
0815: if (richText == null) {
0816: return null;
0817: }
0818:
0819: if (String.class
0820: .equals(flavor.getRepresentationClass())) {
0821: return richText;
0822: } else if (Reader.class.equals(flavor
0823: .getRepresentationClass())) {
0824: return new StringReader(richText);
0825: } else if (InputStream.class.equals(flavor
0826: .getRepresentationClass())) {
0827: return new ByteArrayInputStream(richText.getBytes());
0828: }
0829: throw new UnsupportedFlavorException(flavor);
0830: }
0831:
0832: Position p0;
0833: Position p1;
0834: String mimeType;
0835: String richText;
0836: JTextComponent c;
0837: }
0838: }
0839:
0840: private static class BasicTransferable implements Transferable,
0841: UIResource {
0842:
0843: protected String plainData;
0844: protected String htmlData;
0845: private static DataFlavor[] htmlFlavors;
0846: private static DataFlavor[] stringFlavors;
0847: private static DataFlavor[] plainFlavors;
0848:
0849: static {
0850: try {
0851: htmlFlavors = new DataFlavor[3];
0852: htmlFlavors[0] = new DataFlavor(
0853: "text/html;class=java.lang.String"); //NOI18N
0854: htmlFlavors[1] = new DataFlavor(
0855: "text/html;class=java.io.Reader"); //NOI18N
0856: htmlFlavors[2] = new DataFlavor(
0857: "text/html;charset=unicode;class=java.io.InputStream"); //NOI18N
0858:
0859: plainFlavors = new DataFlavor[3];
0860: plainFlavors[0] = new DataFlavor(
0861: "text/plain;class=java.lang.String"); //NOI18N
0862: plainFlavors[1] = new DataFlavor(
0863: "text/plain;class=java.io.Reader"); //NOI18N
0864: plainFlavors[2] = new DataFlavor(
0865: "text/plain;charset=unicode;class=java.io.InputStream"); //NOI18N
0866:
0867: stringFlavors = new DataFlavor[2];
0868: stringFlavors[0] = new DataFlavor(
0869: DataFlavor.javaJVMLocalObjectMimeType
0870: + ";class=java.lang.String"); //NOI18N
0871: stringFlavors[1] = DataFlavor.stringFlavor;
0872:
0873: } catch (ClassNotFoundException cle) {
0874: System.err
0875: .println("error initializing javax.swing.plaf.basic.BasicTranserable"); ////NOI18N
0876: }
0877: }
0878:
0879: public BasicTransferable(String plainData, String htmlData) {
0880: this .plainData = plainData;
0881: this .htmlData = htmlData;
0882: }
0883:
0884: /**
0885: * Returns an array of DataFlavor objects indicating the flavors the data
0886: * can be provided in. The array should be ordered according to preference
0887: * for providing the data (from most richly descriptive to least descriptive).
0888: * @return an array of data flavors in which this data can be transferred
0889: */
0890: public DataFlavor[] getTransferDataFlavors() {
0891: DataFlavor[] richerFlavors = getRicherFlavors();
0892: int nRicher = (richerFlavors != null) ? richerFlavors.length
0893: : 0;
0894: int nHTML = (isHTMLSupported()) ? htmlFlavors.length : 0;
0895: int nPlain = (isPlainSupported()) ? plainFlavors.length : 0;
0896: int nString = (isPlainSupported()) ? stringFlavors.length
0897: : 0;
0898: int nFlavors = nRicher + nHTML + nPlain + nString;
0899: DataFlavor[] flavors = new DataFlavor[nFlavors];
0900:
0901: // fill in the array
0902: int nDone = 0;
0903: if (nRicher > 0) {
0904: System.arraycopy(richerFlavors, 0, flavors, nDone,
0905: nRicher);
0906: nDone += nRicher;
0907: }
0908: if (nHTML > 0) {
0909: System.arraycopy(htmlFlavors, 0, flavors, nDone, nHTML);
0910: nDone += nHTML;
0911: }
0912: if (nPlain > 0) {
0913: System.arraycopy(plainFlavors, 0, flavors, nDone,
0914: nPlain);
0915: nDone += nPlain;
0916: }
0917: if (nString > 0) {
0918: System.arraycopy(stringFlavors, 0, flavors, nDone,
0919: nString);
0920: nDone += nString;
0921: }
0922: return flavors;
0923: }
0924:
0925: /**
0926: * Returns whether or not the specified data flavor is supported for
0927: * this object.
0928: * @param flavor the requested flavor for the data
0929: * @return boolean indicating whether or not the data flavor is supported
0930: */
0931: public boolean isDataFlavorSupported(DataFlavor flavor) {
0932: DataFlavor[] flavors = getTransferDataFlavors();
0933: for (int i = 0; i < flavors.length; i++) {
0934: if (flavors[i].equals(flavor)) {
0935: return true;
0936: }
0937: }
0938: return false;
0939: }
0940:
0941: /**
0942: * Returns an object which represents the data to be transferred. The class
0943: * of the object returned is defined by the representation class of the flavor.
0944: *
0945: * @param flavor the requested flavor for the data
0946: * @see DataFlavor#getRepresentationClass
0947: * @exception IOException if the data is no longer available
0948: * in the requested flavor.
0949: * @exception UnsupportedFlavorException if the requested data flavor is
0950: * not supported.
0951: */
0952: public Object getTransferData(DataFlavor flavor)
0953: throws UnsupportedFlavorException, IOException {
0954: DataFlavor[] richerFlavors = getRicherFlavors();
0955: if (isRicherFlavor(flavor)) {
0956: return getRicherData(flavor);
0957: } else if (isHTMLFlavor(flavor)) {
0958: String data = getHTMLData();
0959: data = (data == null) ? "" : data; //NOI18N
0960: if (String.class
0961: .equals(flavor.getRepresentationClass())) {
0962: return data;
0963: } else if (Reader.class.equals(flavor
0964: .getRepresentationClass())) {
0965: return new StringReader(data);
0966: } else if (InputStream.class.equals(flavor
0967: .getRepresentationClass())) {
0968: return new ByteArrayInputStream(data.getBytes());
0969: }
0970: // fall through to unsupported
0971: } else if (isPlainFlavor(flavor)) {
0972: String data = getPlainData();
0973: data = (data == null) ? "" : data;
0974: if (String.class
0975: .equals(flavor.getRepresentationClass())) {
0976: return data;
0977: } else if (Reader.class.equals(flavor
0978: .getRepresentationClass())) {
0979: return new StringReader(data);
0980: } else if (InputStream.class.equals(flavor
0981: .getRepresentationClass())) {
0982: return new ByteArrayInputStream(data.getBytes());
0983: }
0984: // fall through to unsupported
0985:
0986: } else if (isStringFlavor(flavor)) {
0987: String data = getPlainData();
0988: data = (data == null) ? "" : data; //NOI18N
0989: return data;
0990: }
0991: throw new UnsupportedFlavorException(flavor);
0992: }
0993:
0994: // --- richer subclass flavors ----------------------------------------------
0995: protected boolean isRicherFlavor(DataFlavor flavor) {
0996: DataFlavor[] richerFlavors = getRicherFlavors();
0997: int nFlavors = (richerFlavors != null) ? richerFlavors.length
0998: : 0;
0999: for (int i = 0; i < nFlavors; i++) {
1000: if (richerFlavors[i].equals(flavor)) {
1001: return true;
1002: }
1003: }
1004: return false;
1005: }
1006:
1007: /**
1008: * Some subclasses will have flavors that are more descriptive than HTML
1009: * or plain text. If this method returns a non-null value, it will be
1010: * placed at the start of the array of supported flavors.
1011: */
1012: protected DataFlavor[] getRicherFlavors() {
1013: return null;
1014: }
1015:
1016: protected Object getRicherData(DataFlavor flavor)
1017: throws UnsupportedFlavorException {
1018: return null;
1019: }
1020:
1021: // --- html flavors ----------------------------------------------------------
1022: /**
1023: * Returns whether or not the specified data flavor is an HTML flavor that
1024: * is supported.
1025: * @param flavor the requested flavor for the data
1026: * @return boolean indicating whether or not the data flavor is supported
1027: */
1028: protected boolean isHTMLFlavor(DataFlavor flavor) {
1029: DataFlavor[] flavors = htmlFlavors;
1030: for (int i = 0; i < flavors.length; i++) {
1031: if (flavors[i].equals(flavor)) {
1032: return true;
1033: }
1034: }
1035: return false;
1036: }
1037:
1038: /**
1039: * Should the HTML flavors be offered? If so, the method
1040: * getHTMLData should be implemented to provide something reasonable.
1041: */
1042: protected boolean isHTMLSupported() {
1043: return htmlData != null;
1044: }
1045:
1046: /**
1047: * Fetch the data in a text/html format
1048: */
1049: protected String getHTMLData() {
1050: return htmlData;
1051: }
1052:
1053: // --- plain text flavors ----------------------------------------------------
1054: /**
1055: * Returns whether or not the specified data flavor is an plain flavor that
1056: * is supported.
1057: * @param flavor the requested flavor for the data
1058: * @return boolean indicating whether or not the data flavor is supported
1059: */
1060: protected boolean isPlainFlavor(DataFlavor flavor) {
1061: DataFlavor[] flavors = plainFlavors;
1062: for (int i = 0; i < flavors.length; i++) {
1063: if (flavors[i].equals(flavor)) {
1064: return true;
1065: }
1066: }
1067: return false;
1068: }
1069:
1070: /**
1071: * Should the plain text flavors be offered? If so, the method
1072: * getPlainData should be implemented to provide something reasonable.
1073: */
1074: protected boolean isPlainSupported() {
1075: return plainData != null;
1076: }
1077:
1078: /**
1079: * Fetch the data in a text/plain format.
1080: */
1081: protected String getPlainData() {
1082: return plainData;
1083: }
1084:
1085: // --- string flavorss --------------------------------------------------------
1086: /**
1087: * Returns whether or not the specified data flavor is a String flavor that
1088: * is supported.
1089: * @param flavor the requested flavor for the data
1090: * @return boolean indicating whether or not the data flavor is supported
1091: */
1092: protected boolean isStringFlavor(DataFlavor flavor) {
1093: DataFlavor[] flavors = stringFlavors;
1094: for (int i = 0; i < flavors.length; i++) {
1095: if (flavors[i].equals(flavor)) {
1096: return true;
1097: }
1098: }
1099: return false;
1100: }
1101: }
1102: // END of fix of issue #43309
1103: }
|