/*
* TextAreaTransferHandler.java - Drag and drop support
* :tabSize=4:indentSize=4:noTabs=false:
* :folding=explicit:collapseFolds=1:
*
* Copyright (C) 2004 Slava Pestov
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.gjt.sp.jedit.textarea;
//{{{ Imports
import org.gjt.sp.jedit.*;
import org.gjt.sp.jedit.bufferio.IoTask;
import org.gjt.sp.jedit.bufferset.BufferSetManager;
import org.gjt.sp.jedit.browser.VFSBrowser;
import org.gjt.sp.jedit.io.FileVFS;
import org.gjt.sp.jedit.io.VFS;
import org.gjt.sp.jedit.io.VFSManager;
import org.gjt.sp.util.AwtRunnableQueue;
import org.gjt.sp.util.Log;
import org.gjt.sp.util.ThreadUtilities;
import javax.swing.*;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.io.File;
import java.net.URI;
import java.util.List;
//}}}
/**
* @author Slava Pestov
* @version $Id$
*/
public class TextAreaTransferHandler extends TransferHandler
{
/* I assume that there can be only one drag operation at the time */
private static JEditTextArea dragSource;
private static boolean compoundEdit;
private static boolean sameTextArea;
private static int insertPos;
private static int insertOffset;
// Unfortunately, this does not work, as this DataFlavor is internally changed into another DataFlavor which does not match the intented DataFlavor anymore. :-( So, below, we are iterating.
/*
protected static DataFlavor textURIlistDataFlavor = null;
static {
try {
textURIlistDataFlavor = new DataFlavor("text/uri-list;representationclass=java.lang.String");
} catch (ClassNotFoundException e) {
throw new RuntimeException("Cannot create DataFlavor. This should not happen.",e);
}
}
*/
//{{{ createTransferable
@Override
protected Transferable createTransferable(JComponent c)
{
Log.log(Log.DEBUG,this,"createTransferable()");
JEditTextArea textArea = (JEditTextArea)c;
if(textArea.getSelectionCount() == 0)
return null;
else
{
dragSource = textArea;
return new TextAreaSelection(textArea);
}
} //}}}
//{{{ getSourceActions
@Override
public int getSourceActions(JComponent c)
{
return COPY_OR_MOVE;
} //}}}
//{{{ importData
@Override
public boolean importData(JComponent c, Transferable t)
{
Log.log(Log.DEBUG,this,"Import data");
// Log.log(Log.DEBUG,this,"Import data: t.isDataFlavorSupported("+textURIlistDataFlavor+")="+t.isDataFlavorSupported(textURIlistDataFlavor)+".");
if(!canImport(c,t.getTransferDataFlavors()))
return false;
boolean returnValue;
try
{
if(t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
{
returnValue = importFile(c,t);
}
else
{
DataFlavor uriListStringDataFlavor = null;
DataFlavor[] dataFlavors = t.getTransferDataFlavors();
for (DataFlavor dataFlavor : dataFlavors)
{
if (isUriList(dataFlavor))
{
uriListStringDataFlavor = dataFlavor;
break;
}
}
if (uriListStringDataFlavor != null &&t.isDataFlavorSupported(uriListStringDataFlavor))
{
returnValue = importURIList(c,t,uriListStringDataFlavor);
}
else
{
returnValue = importText(c,t);
}
}
}
catch(Exception e)
{
Log.log(Log.ERROR,this,e);
returnValue = false;
}
GUIUtilities.getView(c).toFront();
GUIUtilities.getView(c).requestFocus();
c.requestFocus();
return returnValue;
} //}}}
//{{{ importFile
private boolean importFile(JComponent c, Transferable t)
throws Exception
{
Log.log(Log.DEBUG,this,"=> File list");
EditPane editPane = (EditPane)
GUIUtilities.getComponentParent(
c,EditPane.class);
View view = editPane.getView();
Buffer buffer = null;
// per the Java API, javaFileListFlavor guarantees that a
// List<File> will be returned. So suppress warning for this
// statement. We know what we're doing.
@SuppressWarnings("unchecked")
List<File> data = (List<File>) t.getTransferData(DataFlavor.javaFileListFlavor);
boolean browsedDirectory = false;
BufferSetManager bufferSetManager = jEdit.getBufferSetManager();
for (File file : data)
{
if (file.isDirectory())
{
if (!browsedDirectory)
{
VFSBrowser.browseDirectory(view, file.getPath());
browsedDirectory = true;
}
continue;
}
Buffer _buffer = jEdit.openFile(editPane, file.getPath());
if (_buffer != null)
{
buffer = _buffer;
bufferSetManager.addBuffer(editPane, buffer);
}
}
if(buffer != null)
editPane.setBuffer(buffer);
view.toFront();
view.requestFocus();
editPane.requestFocus();
return true;
} //}}}
//{{{ importURIList
private boolean importURIList(JComponent c, Transferable t,DataFlavor uriListStringDataFlavor)
throws Exception
{
String str = (String) t.getTransferData(uriListStringDataFlavor);
Log.log(Log.DEBUG,this,"=> URIList \""+str+ '\"');
EditPane editPane = (EditPane) GUIUtilities.getComponentParent(c, EditPane.class);
View view = editPane.getView();
JEditTextArea textArea = (JEditTextArea) c;
if (dragSource == null)
{
boolean found = false;
String[] components = str.split("\r\n");
boolean browsedDirectory = false;
for (int i = 0;i<components.length;i++)
{
String str0 = components[i];
// gnome-commander adds a 0 byte at the end of the file name, discard it
int len = str0.length();
if (len > 0 && (int)str0.charAt(len - 1) == 0)
str0 = str0.substring(0, len - 1);
if (str0.length() > 0)
{
URI uri = new URI(str0); // this handles the URI-decoding
if ("file".equals(uri.getScheme()))
{
File file = new File(uri.getPath());
if (file.isDirectory())
{
if (!browsedDirectory)
{
VFSBrowser.browseDirectory(view, file.getPath());
browsedDirectory = true;
}
}
else
{
AwtRunnableQueue.INSTANCE.runAfterIoTasks(new DraggedURLLoader(textArea,uri.getPath()));
}
found = true;
}
else
{
Log.log(Log.DEBUG,this,"I do not know how to handle this URI "+uri+", ignoring.");
}
}
else
{
// This should be the last component, because every URI in the list is terminated with a "\r\n", even the last one.
if (i!=components.length-1)
{
Log.log(Log.DEBUG,this,"Odd: there is an empty line in the uri list which is not the last line.");
}
}
}
if (found)
{
return true;
}
}
return true;
} //}}}
//{{{ importText
private boolean importText(JComponent c, Transferable t)
throws Exception
{
String str = (String)t.getTransferData(
DataFlavor.stringFlavor);
str = str.trim();
Log.log(Log.DEBUG,this,"=> String \""+str+ '\"');
JEditTextArea textArea = (JEditTextArea)c;
if (dragSource == null)
{
boolean found = false;
String[] components = str.split("\n");
for (String str0 : components)
{
// Only examine the string for a URL if it came from
// outside of jEdit.
VFS vfs = VFSManager.getVFSForPath(str0);
if (!(vfs instanceof FileVFS) || str.startsWith("file://"))
{
// str = str.replace('\n',' ').replace('\r',' ').trim();
if (str0.startsWith("file://"))
{
str0 = str0.substring(7);
}
ThreadUtilities.runInBackground(new DraggedURLLoader(textArea, str0));
}
found = true;
}
if (found)
return true;
}
if(dragSource != null
&& textArea.getBuffer()
== dragSource.getBuffer())
{
compoundEdit = true;
textArea.getBuffer().beginCompoundEdit();
}
sameTextArea = textArea == dragSource;
int caret = textArea.getCaretPosition();
Selection s = textArea.getSelectionAtOffset(caret);
/* if user drops into the same
selection where they started, do
nothing. */
if(s != null)
{
if(sameTextArea)
return false;
/* if user drops into a selection,
replace selection */
int startPos = s.start;
textArea.setSelectedText(s,str);
textArea.setSelection(new Selection.Range(startPos,startPos+str.length()));
}
/* otherwise just insert the text */
else
{
if (sameTextArea)
{
insertPos = caret;
insertOffset = 0;
Selection[] selections = textArea.getSelection();
for (Selection selection : selections)
{
if (selection.end < insertPos + insertOffset)
insertOffset -= selection.end - selection.start;
}
}
else
{
textArea.getBuffer().insert(caret,str);
textArea.setSelection(new Selection.Range(caret,caret+str.length()));
}
}
textArea.scrollToCaret(true);
return true;
} //}}}
//{{{ exportDone() method
@Override
protected void exportDone(JComponent c, Transferable t,
int action)
{
Log.log(Log.DEBUG,this,"Export done");
JEditTextArea textArea = (JEditTextArea)c;
try
{
// This happens if importData returns false. For example if you drop into the Selection
if (action == NONE)
{
Log.log(Log.DEBUG,this,"Export impossible");
return;
}
if(t == null)
{
Log.log(Log.DEBUG,this,"=> Null transferrable");
textArea.selectNone();
}
else if(t.isDataFlavorSupported(
DataFlavor.stringFlavor))
{
Log.log(Log.DEBUG,this,"=> String");
if (sameTextArea)
{
if(action == MOVE)
{
textArea.setSelectedText(null,false);
insertPos += insertOffset;
}
try
{
String str = (String)t.getTransferData(DataFlavor.stringFlavor);
textArea.getBuffer().insert(insertPos,str);
textArea.setSelection(new Selection.Range(insertPos,insertPos+str.length()));
}
catch(Exception e)
{
Log.log(Log.DEBUG,this,"exportDone in sameTextArea");
Log.log(Log.DEBUG,this,e);
}
}
else
{
if(action == MOVE)
textArea.setSelectedText(null,false);
else
textArea.selectNone();
}
}
}
finally
{
if(compoundEdit)
{
compoundEdit = false;
textArea.getBuffer().endCompoundEdit();
}
}
dragSource = null;
} //}}}
//{{{ isUriList() method
private boolean isUriList(DataFlavor flavor)
{
return ("text".equals(flavor.getPrimaryType()) &&
"uri-list".equals(flavor.getSubType()) &&
flavor.getRepresentationClass() == String.class);
} //}}}
//{{{ canImport() methods
@Override
public boolean canImport(TransferSupport support)
{
if (dragSource != null)
return true;
else
{
support.setDropAction(COPY);
return super.canImport(support);
}
}
@Override
public boolean canImport(JComponent c, DataFlavor[] flavors)
{
JEditTextArea textArea = (JEditTextArea)c;
// correctly handle text flavor + file list flavor
// + text area read only, do an or of all flags
boolean returnValue = false;
for (DataFlavor flavor : flavors)
{
if (flavor.equals(DataFlavor.javaFileListFlavor) ||
isUriList(flavor))
{
returnValue = true;
break;
} else if (flavor.equals(
DataFlavor.stringFlavor))
{
if (textArea.isEditable())
{
returnValue = true;
break;
}
}
}
Log.log(Log.DEBUG,this,"canImport() returning "
+ returnValue);
return returnValue;
} //}}}
//{{{ TextAreaSelection class
private static class TextAreaSelection extends StringSelection
{
final JEditTextArea textArea;
TextAreaSelection(JEditTextArea textArea)
{
super(textArea.getSelectedText());
this.textArea = textArea;
}
} //}}}
//{{{ DraggedURLLoader class
private static class DraggedURLLoader extends IoTask
{
private final JEditTextArea textArea;
private final String url;
DraggedURLLoader(JEditTextArea textArea, String url)
{
super();
this.textArea = textArea;
this.url = url;
}
public void _run()
{
EditPane editPane = EditPane.get(textArea);
jEdit.openFile(editPane,url);
}
} //}}}
}