/*
* Registers.java - Register manager
* :tabSize=4:indentSize=4:noTabs=false:
* :folding=explicit:collapseFolds=1:
*
* Copyright (C) 1999, 2003 Slava Pestov
* Portions Copyright (C) 2010 Matthieu Casanova
*
* 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;
//{{{ Imports
import java.awt.datatransfer.*;
import java.awt.Toolkit;
import java.io.*;
import org.gjt.sp.jedit.buffer.JEditBuffer;
import org.gjt.sp.jedit.datatransfer.JEditDataFlavor;
import org.gjt.sp.jedit.datatransfer.JEditRichText;
import org.gjt.sp.jedit.datatransfer.TransferHandler;
import org.gjt.sp.jedit.gui.HistoryModel;
import org.gjt.sp.jedit.textarea.TextArea;
import org.gjt.sp.jedit.textarea.Selection;
import org.gjt.sp.util.Log;
//}}}
/**
* jEdit's registers are an extension of the clipboard metaphor.<p>
*
* A {@link Registers.Register} is string of text indexed by a
* single character. Typically the text is taken from selected buffer text
* and the index character is a keyboard character selected by the user.<p>
*
* This class defines a number of static methods
* that give each register the properties of a virtual clipboard.<p>
*
* Two classes implement the {@link Registers.Register} interface. A
* {@link Registers.ClipboardRegister} is tied to the contents of the
* system clipboard. jEdit assigns a
* {@link Registers.ClipboardRegister} to the register indexed under
* the character <code>$</code>. A
* {@link Registers.DefaultRegister} is created for registers assigned
* by the user. In addition, jEdit assigns <code>%</code> to
* the last text segment selected in the text area. On Windows this is a
* {@link Registers.DefaultRegister}, on Unix under Java 2 version 1.4, a
* {@link Registers.ClipboardRegister}.
*
* @author Slava Pestov
* @author John Gellene (API documentation)
* @version $Id$
*/
public class Registers
{
//{{{ copy() method
/**
* Copies the text selected in the text area into the specified register.
* This will replace the existing contents of the designated register.
*
* @param textArea The text area
* @param register The register
* @since jEdit 2.7pre2
*/
public static void copy(TextArea textArea, char register)
{
String selection = textArea.getSelectedText();
if(selection == null)
return;
Transferable transferable = TransferHandler.getInstance().getTransferable(textArea, selection);
setRegister(register, transferable);
HistoryModel.getModel("clipboard").addItem(selection);
} //}}}
//{{{ cut() method
/**
* Copies the text selected in the text area into the specified
* register, and then removes it from the buffer.
*
* @param textArea The text area
* @param register The register
* @since jEdit 2.7pre2
*/
public static void cut(TextArea textArea, char register)
{
if(textArea.isEditable())
{
String selection = textArea.getSelectedText();
if(selection == null)
return;
Transferable transferable = TransferHandler.getInstance().getTransferable(textArea, selection);
setRegister(register,transferable);
HistoryModel.getModel("clipboard").addItem(selection);
textArea.setSelectedText("");
}
else
javax.swing.UIManager.getLookAndFeel().provideErrorFeedback(null);
} //}}}
//{{{ append() methods
/**
* Appends the text selected in the text area to the specified register,
* with a newline between the old and new text.
* @param textArea The text area
* @param register The register
*/
public static void append(TextArea textArea, char register)
{
append(textArea,register,"\n",false);
}
/**
* Appends the text selected in the text area to the specified register.
* @param textArea The text area
* @param register The register
* @param separator The separator to insert between the old and new text
*/
public static void append(TextArea textArea, char register,
String separator)
{
append(textArea,register,separator,false);
}
/**
* Appends the text selected in the text area to the specified register.
* @param textArea The text area
* @param register The register
* @param separator The text to insert between the old and new text
* @param cut Should the current selection be removed?
* @since jEdit 3.2pre1
*/
public static void append(TextArea textArea, char register,
String separator, boolean cut)
{
if(cut && !textArea.isEditable())
{
javax.swing.UIManager.getLookAndFeel().provideErrorFeedback(null);
return;
}
String selection = textArea.getSelectedText();
if(selection == null)
return;
Register reg = getRegister(register);
if(reg != null)
{
Transferable transferable = reg.getTransferable();
if (transferable.isDataFlavorSupported(DataFlavor.stringFlavor))
{
try
{
String registerContents = (String) transferable.getTransferData(DataFlavor.stringFlavor);
if(registerContents != null)
{
if(registerContents.endsWith(separator))
selection = registerContents + selection;
else
selection = registerContents + separator + selection;
}
}
catch (UnsupportedFlavorException e)
{
}
catch (IOException e)
{
Log.log(Log.ERROR, Registers.class, e);
}
}
}
Transferable transferable = TransferHandler.getInstance().getTransferable(textArea, selection);
setRegister(register,transferable);
HistoryModel.getModel("clipboard").addItem(selection);
if(cut)
textArea.setSelectedText("");
} //}}}
//{{{ paste() methods
/**
* Insets the contents of the specified register into the text area.
* @param textArea The text area
* @param register The register
* @since jEdit 2.7pre2
*/
public static void paste(TextArea textArea, char register)
{
paste(textArea,register,false);
}
/**
* Insets the contents of the specified register into the text area.
* @param textArea The text area
* @param register The register
* @param preferredDataFlavor the preferred dataflavor. If not available
* <tt>DataFlavor.stringFlavor</tt> will be used
* @since jEdit 4.4pre1
*/
public static void paste(TextArea textArea, char register, DataFlavor preferredDataFlavor)
{
paste(textArea,register,false, preferredDataFlavor);
}
/**
* Inserts the contents of the specified register into the text area.
* @param textArea The text area
* @param register The register
* @param vertical Vertical (columnar) paste
* @since jEdit 4.1pre1
*/
public static void paste(TextArea textArea, char register,
boolean vertical)
{
if(!textArea.isEditable())
{
javax.swing.UIManager.getLookAndFeel().provideErrorFeedback(null);
return;
}
Register reg = getRegister(register);
if(reg == null)
{
javax.swing.UIManager.getLookAndFeel().provideErrorFeedback(null);
return;
}
Transferable transferable = reg.getTransferable();
Mode mode = null;
String selection = null;
if (transferable.isDataFlavorSupported(JEditDataFlavor.jEditRichTextDataFlavor))
{
try
{
JEditRichText data = (JEditRichText) transferable.getTransferData(JEditDataFlavor.jEditRichTextDataFlavor);
mode = data.getMode();
selection = data.getText();
}
catch (UnsupportedFlavorException e)
{
Log.log(Log.ERROR, Registers.class, e);
}
catch (IOException e)
{
Log.log(Log.ERROR, Registers.class, e);
}
}
else if (transferable.isDataFlavorSupported(DataFlavor.stringFlavor))
{
selection = getTextFromTransferable(transferable, DataFlavor.stringFlavor);
}
if(selection == null)
{
javax.swing.UIManager.getLookAndFeel().provideErrorFeedback(null);
return;
}
JEditBuffer buffer = textArea.getBuffer();
applyMode(mode, buffer);
_paste(textArea, vertical, selection, buffer);
}
/**
* Inserts the contents of the specified register into the text area.
* @param textArea The text area
* @param register The register
* @param vertical Vertical (columnar) paste
* @param preferredDataFlavor the preferred dataflavor. If not available
* <tt>DataFlavor.stringFlavor</tt> will be used
* @since jEdit 4.4pre1
*/
public static void paste(TextArea textArea, char register,
boolean vertical, DataFlavor preferredDataFlavor)
{
if (JEditDataFlavor.jEditRichTextDataFlavor.equals(preferredDataFlavor))
{
paste(textArea,register,vertical);
return;
}
if(!textArea.isEditable())
{
javax.swing.UIManager.getLookAndFeel().provideErrorFeedback(null);
return;
}
Register reg = getRegister(register);
if(reg == null)
{
javax.swing.UIManager.getLookAndFeel().provideErrorFeedback(null);
return;
}
Transferable transferable = reg.getTransferable();
String selection = null;
if (transferable.isDataFlavorSupported(preferredDataFlavor))
{
selection = getTextFromTransferable(transferable, preferredDataFlavor);
}
else if (transferable.isDataFlavorSupported(DataFlavor.stringFlavor))
{
selection = getTextFromTransferable(transferable, DataFlavor.stringFlavor);
}
if(selection == null)
{
javax.swing.UIManager.getLookAndFeel().provideErrorFeedback(null);
return;
}
JEditBuffer buffer = textArea.getBuffer();
/*
Commented because it must not use jEdit class.
Need to rewrite a property manager that is independant
String mime = preferredDataFlavor.getMimeType();
int i = mime.indexOf(';');
if (i != -1)
{
mime = mime.substring(0,i);
}
String mode = jEdit.getProperty("mime2mode."+mime);
if (mode != null)
{
Mode _mode = ModeProvider.instance.getMode(mode);
if (_mode != null)
{
applyMode(_mode, buffer);
}
} */
_paste(textArea, vertical, selection, buffer);
}
private static void _paste(TextArea textArea, boolean vertical, String selection, JEditBuffer buffer)
{
try
{
buffer.beginCompoundEdit();
/* vertical paste */
if(vertical && textArea.getSelectionCount() == 0)
{
int caret = textArea.getCaretPosition();
int caretLine = textArea.getCaretLine();
Selection.Rect rect = new Selection.Rect(
caretLine,caret,caretLine,caret);
textArea.setSelectedText(rect,selection);
caretLine = textArea.getCaretLine();
if(caretLine != textArea.getLineCount() - 1)
{
int startColumn = rect.getStartColumn(
buffer);
int offset = buffer
.getOffsetOfVirtualColumn(
caretLine + 1,startColumn,null);
if(offset == -1)
{
buffer.insertAtColumn(caretLine + 1,startColumn,"");
textArea.setCaretPosition(
buffer.getLineEndOffset(
caretLine + 1) - 1);
}
else
{
textArea.setCaretPosition(
buffer.getLineStartOffset(
caretLine + 1) + offset);
}
}
}
else /* Regular paste */
{
textArea.replaceSelection(selection);
}
}
finally
{
buffer.endCompoundEdit();
}
HistoryModel.getModel("clipboard").addItem(selection);
} //}}}
//{{{ applyMode() method
private static void applyMode(Mode mode, JEditBuffer buffer)
{
if (mode != null &&
"text".equals(buffer.getMode().getName()) &&
!mode.equals(buffer.getMode()) &&
buffer.getLength() == 0)
{
buffer.setMode(mode);
}
} //}}}
//{{{ getTextFromTransferable() method
private static String getTextFromTransferable(Transferable transferable, DataFlavor dataFlavor)
{
try
{
Object data = transferable.getTransferData(dataFlavor);
return stripEOLChars(data.toString());
}
catch (UnsupportedFlavorException e)
{
Log.log(Log.ERROR, Registers.class, e);
}
catch (IOException e)
{
Log.log(Log.ERROR, Registers.class, e);
}
return null;
} //}}}
//{{{ getRegister() method
/**
* Returns the specified register.
* @param name The name
*/
public static Register getRegister(char name)
{
if(name != '$' && name != '%')
{
if(!loaded)
loadRegisters();
}
if(registers == null || name >= registers.length)
return null;
else
return registers[name];
} //}}}
//{{{ setRegister() methods
/**
* Sets the specified register.
* @param name The name
* @param newRegister The new value
*/
public static void setRegister(char name, Register newRegister)
{
touchRegister(name);
if(name >= registers.length)
{
Register[] newRegisters = new Register[
Math.min(1<<16, name<<1)];
System.arraycopy(registers,0,newRegisters,0,
registers.length);
registers = newRegisters;
}
registers[name] = newRegister;
if (listener != null)
listener.registerChanged(name);
}
/**
* Sets the specified register.
* @param name The name
* @param value The new value
*/
public static void setRegister(char name, String value)
{
setRegister(name, new StringSelection(value));
}
/**
* Sets the specified register.
* @param name The name
* @param transferable the transferable
*/
public static void setRegister(char name, Transferable transferable)
{
touchRegister(name);
Register register = getRegister(name);
if(register != null)
{
register.setTransferable(transferable);
if (listener != null)
listener.registerChanged(name);
}
else
{
Register defaultRegister = new DefaultRegister();
defaultRegister.setTransferable(transferable);
setRegister(name, defaultRegister);
}
} //}}}
//{{{ clearRegister() method
/**
* Sets the value of the specified register to <code>null</code>.
* @param name The register name
*/
public static void clearRegister(char name)
{
if(name >= registers.length)
return;
Register register = registers[name];
if(name == '$' || name == '%')
register.setTransferable(new StringSelection(""));
else
{
registers[name] = null;
modified = true;
if (listener != null)
listener.registerChanged(name);
}
} //}}}
//{{{ getRegisters() method
/**
* Returns an array of all available registers. Some of the elements
* of this array might be <code>null</code>.
*/
public static Register[] getRegisters()
{
if(!loaded)
loadRegisters();
return registers;
} //}}}
//{{{ getRegisterNameString() method
/**
* Returns a string of all defined registers, used by the status bar
* (eg, "a b $ % ^").
* @since jEdit 4.2pre2
*/
public static String getRegisterNameString()
{
if(!loaded)
loadRegisters();
StringBuilder buf = new StringBuilder(registers.length << 1);
for(int i = 0; i < registers.length; i++)
{
if(registers[i] != null)
{
if(buf.length() != 0)
buf.append(' ');
buf.append((char)i);
}
}
if(buf.length() == 0)
return null;
else
return buf.toString();
} //}}}
//{{{ saveRegisters() method
public static void saveRegisters()
{
if(!loaded || !modified)
return;
if (saver != null)
{
saver.saveRegisters();
modified = false;
}
} //}}}
//{{{ setListener() method
public static void setListener(RegistersListener listener)
{
Registers.listener = listener;
} //}}}
//{{{ setSaver() method
public static void setSaver(RegisterSaver saver)
{
Registers.saver = saver;
} //}}}
//{{{ isLoading() method
public static boolean isLoading()
{
return loading;
} //}}}
//{{{ setLoading() method
public static void setLoading(boolean loading)
{
Registers.loading = loading;
} //}}}
//{{{ Private members
private static Register[] registers;
private static boolean loaded, loading;
private static RegisterSaver saver;
private static RegistersListener listener;
/**
* Flag that tell if a register has been modified (except for '%' and '$' registers that aren't
* saved to the xml file).
*/
private static boolean modified;
private Registers() {}
static
{
registers = new Register[256];
Toolkit toolkit = Toolkit.getDefaultToolkit();
registers['$'] = new ClipboardRegister(
toolkit.getSystemClipboard());
Clipboard selection = toolkit.getSystemSelection();
if(selection != null)
registers['%'] = new ClipboardRegister(selection);
}
//{{{ touchRegister() method
private static void touchRegister(char name)
{
if(name == '%' || name == '$')
return;
if(!loaded)
loadRegisters();
if(!loading)
modified = true;
} //}}}
//{{{ loadRegisters() method
private static void loadRegisters()
{
if (saver != null)
{
loaded = true;
saver.loadRegisters();
}
} //}}}
//{{{ loadRegisters() method
private static String stripEOLChars(String selection) throws IOException
{
boolean trailingEOL = selection.endsWith("\n")
|| selection.endsWith(System.getProperty(
"line.separator"));
// Some Java versions return the clipboard
// contents using the native line separator,
// so have to convert it here
BufferedReader in = new BufferedReader(
new StringReader(selection));
StringBuilder buf = new StringBuilder(selection.length());
String line;
while((line = in.readLine()) != null)
{
// broken Eclipse workaround!
// 24 Febuary 2004
if(line.endsWith("\0"))
{
line = line.substring(0,
line.length() - 1);
}
buf.append(line);
buf.append('\n');
}
// remove trailing \n
if(!trailingEOL && buf.length() != 0)
buf.setLength(buf.length() - 1);
return buf.toString();
} //}}}
//}}}
//{{{ Inner classes
//{{{ Register interface
/**
* A register.
*/
public interface Register
{
/**
* Converts to a string.
*/
//@Deprecated
// undeprecating this since the two concrete classes defined in Registers
// use it and several other classes depend on it.
// TODO: finish the work to actually deprecate this.
String toString();
/**
* Sets the register contents.
* @deprecated use {@link #setTransferable(java.awt.datatransfer.Transferable)}
* instead, for example
* <code>setTransferable(new StringSelection(""))</code>
*/
@Deprecated
void setValue(String value);
Transferable getTransferable();
void setTransferable(Transferable transferable);
} //}}}
//{{{ ClipboardRegister class
/**
* A clipboard register. Register "$" should always be an
* instance of this.
*/
public static class ClipboardRegister implements Register
{
Clipboard clipboard;
public ClipboardRegister(Clipboard clipboard)
{
this.clipboard = clipboard;
}
/**
* Sets the clipboard contents.
*/
@Override
public void setValue(String value)
{
Transferable selection = new StringSelection(value);
clipboard.setContents(selection, null);
}
/**
* Returns the clipboard contents.
*/
@Override
public String toString()
{
try
{
if (false)
{
/*
This is to debug clipboard problems.
Apparently, jEdit is unable to copy text from clipbard into the current
text buffer if the clipboard was filled using the command
echo test | xselection CLIPBOARD -
under Linux. However, it seems that Java does not offer any
data flavor for this clipboard content (under J2RE 1.5.0_06-b05)
Thus, copying from clipboard seems to be plainly impossible.
*/
Log.log(Log.DEBUG,this,"clipboard.getContents(this)="+clipboard.getContents(this)+'.');
debugListDataFlavors(clipboard.getContents(this));
}
String selection = (String)clipboard.getContents(this).getTransferData(DataFlavor.stringFlavor);
return stripEOLChars(selection);
}
catch(Exception e)
{
Log.log(Log.NOTICE,this,e);
return null;
}
}
@Override
public Transferable getTransferable()
{
return clipboard.getContents(this);
}
@Override
public void setTransferable(Transferable transferable)
{
clipboard.setContents(transferable, null);
}
} //}}}
//{{{ debugListDataFlavors() method
protected static void debugListDataFlavors(Transferable transferable)
{
DataFlavor[] dataFlavors = transferable.getTransferDataFlavors();
for (DataFlavor dataFlavor : dataFlavors)
{
Log.log(Log.DEBUG, Registers.class, "debugListDataFlavors(): dataFlavor=" +
dataFlavor + '.');
}
if (dataFlavors.length == 0)
{
Log.log(Log.DEBUG,Registers.class,
"debugListDataFlavors(): no dataFlavor supported.");
}
} //}}}
//{{{ DefaultRegister class
private static class DefaultRegister implements Register
{
private Transferable transferable;
@Override
public void setValue(String value)
{
transferable = new StringSelection(value);
}
@Override
public String toString()
{
if (transferable == null)
return null;
if (transferable.isDataFlavorSupported(DataFlavor.stringFlavor))
{
try
{
return transferable.getTransferData(DataFlavor.stringFlavor).toString();
}
catch (UnsupportedFlavorException e)
{
Log.log(Log.ERROR, this, e);
}
catch (IOException e)
{
Log.log(Log.ERROR, this, e);
}
}
return transferable.toString();
}
@Override
public Transferable getTransferable()
{
return transferable;
}
@Override
public void setTransferable(Transferable transferable)
{
this.transferable = transferable;
}
} //}}}
//}}}
}