package de.unisiegen.tpml.graphics.editor ;
import java.awt.BorderLayout;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.ClipboardOwner;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.util.Stack;
import javax.swing.JComponent;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JSplitPane;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import org.apache.log4j.Logger;
import de.unisiegen.tpml.core.expressions.Expression;
import de.unisiegen.tpml.core.languages.Language;
import de.unisiegen.tpml.graphics.EditorComponent;
import de.unisiegen.tpml.graphics.SideBar;
import de.unisiegen.tpml.graphics.SideBarListener;
import de.unisiegen.tpml.graphics.StyledLanguageDocument;
import de.unisiegen.tpml.graphics.StyledLanguageEditor;
import de.unisiegen.tpml.graphics.outline.DefaultOutline;
import de.unisiegen.tpml.graphics.outline.Outline;
/**
* The Editor displayed in the Source Tab.
*
* @author Christoph Fehling
* @author Christian Fehler
* @version $Rev$
* @see de.unisiegen.tpml.ui.EditorComponent
*/
@SuppressWarnings("all")
public class TextEditorPanel extends JPanel implements EditorComponent ,
ClipboardOwner
{
private class MenuListener implements ActionListener
{
public void actionPerformed ( ActionEvent evt )
{
String command = evt.getActionCommand ( ) ;
if ( command.equals ( java.util.ResourceBundle.getBundle (
"de/unisiegen/tpml/ui/ui" ).getString ( "Copy" ) ) ) //$NON-NLS-1$ //$NON-NLS-2$
{
handleCopy ( ) ;
}
else if ( command.equals ( java.util.ResourceBundle.getBundle (
"de/unisiegen/tpml/ui/ui" ).getString ( "Cut" ) ) ) //$NON-NLS-1$ //$NON-NLS-2$
{
handleCut ( ) ;
}
else if ( command.equals ( java.util.ResourceBundle.getBundle (
"de/unisiegen/tpml/ui/ui" ).getString ( "Paste" ) ) ) //$NON-NLS-1$ //$NON-NLS-2$
{
handlePaste ( ) ;
}
else if ( command.equals ( java.util.ResourceBundle.getBundle (
"de/unisiegen/tpml/ui/ui" ).getString ( "Undo" ) ) ) //$NON-NLS-1$ //$NON-NLS-2$
{
handleUndo ( ) ;
}
else if ( command.equals ( java.util.ResourceBundle.getBundle (
"de/unisiegen/tpml/ui/ui" ).getString ( "Redo" ) ) ) //$NON-NLS-1$ //$NON-NLS-2$
{
handleRedo ( ) ;
}
}
}
private class PopupListener extends MouseAdapter
{
private void maybeShowPopup ( MouseEvent e )
{
if ( e.isPopupTrigger ( ) )
{
TextEditorPanel.this.popup.show ( e.getComponent ( ) , e.getX ( ) , e
.getY ( ) ) ;
}
}
@ Override
public void mousePressed ( MouseEvent e )
{
maybeShowPopup ( e ) ;
}
@ Override
public void mouseReleased ( MouseEvent e )
{
maybeShowPopup ( e ) ;
}
}
private class TextDocumentListener implements DocumentListener
{
public void changedUpdate ( DocumentEvent arg0 )
{
logger.debug ( "Document was changed" ) ; //$NON-NLS-1$
}
public void insertUpdate ( DocumentEvent arg0 )
{
logger.debug ( "Text inserted into document" ) ; //$NON-NLS-1$
try
{
TextEditorPanel.this.setUndoStatus ( true ) ;
String doctext = arg0.getDocument ( ).getText ( 0 ,
arg0.getDocument ( ).getLength ( ) ) ;
if ( doctext.endsWith ( " " ) )
{
TextEditorPanel.this.undohistory.push ( doctext ) ;
logger.debug ( "history added: " + doctext ) ;
}
setRedoStatus ( false ) ;
TextEditorPanel.this.redohistory.clear ( ) ;
TextEditorPanel.this.currentContent = doctext ;
loadOutlineExpression ( Outline.ExecuteAutoChange.EDITOR ) ;
}
catch ( BadLocationException e )
{
logger.error ( "Failed to add text to undo history" , e ) ;
}
}
public void removeUpdate ( DocumentEvent arg0 )
{
logger.debug ( "Text removed from document" ) ;
try
{
TextEditorPanel.this.setUndoStatus ( true ) ;
TextEditorPanel.this.undohistory
.push ( TextEditorPanel.this.currentContent ) ;
setRedoStatus ( false ) ;
TextEditorPanel.this.redohistory.clear ( ) ;
TextEditorPanel.this.currentContent = arg0.getDocument ( ).getText ( 0 ,
arg0.getDocument ( ).getLength ( ) ) ;
loadOutlineExpression ( Outline.ExecuteAutoChange.EDITOR ) ;
}
catch ( BadLocationException e )
{
logger.error ( "Failed to add text to undo history" , e ) ;
}
}
}
//
// Constants
//
/**
* The {@link Logger} for this class.
*/
static final Logger logger = Logger.getLogger ( TextEditorPanel.class ) ;
/**
* The serial version Identifier.
*/
private static final long serialVersionUID = - 4886621661465144817L ;
//
// Attributes
//
private StyledLanguageEditor editor ;
private StyledLanguageDocument document ;
private JScrollPane scrollpane ;
private SideBar sideBar ;
/**
* The initial content of this file
*/
private String initialContent = "" ;
private String currentContent = "" ;
private Stack < String > undohistory = new Stack < String > ( ) ;
private Stack < String > redohistory = new Stack < String > ( ) ;
private TextDocumentListener doclistener = new TextDocumentListener ( ) ;
private boolean nextStatus ;
private boolean redoStatus ;
private boolean undoStatus ;
private boolean changed ;
private JPopupMenu popup ;
private JMenuItem undoItem ;
private JMenuItem redoItem ;
/**
* The {@link Outline} of this view.
*/
private Outline outline ;
/**
* The <code>JSplitPane</code> for the <code>component</code>.
*/
private JSplitPane jSplitPane ;
/**
* Editor with syntax highlighting and undo/redo history.
*/
public TextEditorPanel ( Language language )
{
if ( language == null )
throw new NullPointerException ( "language is null" ) ;
setLayout ( new BorderLayout ( ) ) ;
initComponents ( language ) ;
}
public void clearHistory ( )
{
undohistory.clear ( ) ;
redohistory.clear ( ) ;
setUndoStatus ( false ) ;
}
public StyledLanguageDocument getDocument ( )
{
return this.document ;
}
public StyledLanguageEditor getEditor ( )
{
return this.editor ;
}
/**
* Returns the jSplitPane.
*
* @return The jSplitPane.
* @see #jSplitPane
*/
public JSplitPane getJSplitPane ( )
{
return this.jSplitPane ;
}
/**
* Returns the outline.
*
* @return The outline.
* @see #outline
*/
public Outline getOutline ( )
{
return this.outline ;
}
public String getSelectedText ( )
{
return this.editor.getSelectedText ( ) ;
}
public String getText ( )
{
try
{
return this.document.getText ( 0 , this.document.getLength ( ) ) ;
}
catch ( BadLocationException e )
{
logger.error ( "Cannot get Text from document" , e ) ;
}
return "" ;
}
public void handleCopy ( )
{
Clipboard clipboard = getToolkit ( ).getSystemClipboard ( ) ;
StringSelection stringSelection = new StringSelection ( getSelectedText ( ) ) ;
clipboard.setContents ( stringSelection , this ) ;
}
public void handleCut ( )
{
Clipboard clipboard = getToolkit ( ).getSystemClipboard ( ) ;
StringSelection stringSelection = new StringSelection ( getSelectedText ( ) ) ;
clipboard.setContents ( stringSelection , this ) ;
removeSelectedText ( ) ;
}
/**
* The Next Function is not implemented!
*
* @author Christoph Fehling
* @version $Rev$
*/
public void handleNext ( )
{
// this function is not implemented here
}
public void handlePaste ( )
{
Clipboard clipboard = getToolkit ( ).getSystemClipboard ( ) ;
Transferable contents = clipboard.getContents ( null ) ;
boolean hasTransferableText = ( contents != null )
&& contents.isDataFlavorSupported ( DataFlavor.stringFlavor ) ;
if ( hasTransferableText )
{
try
{
removeSelectedText ( ) ;
insertText ( ( String ) contents
.getTransferData ( DataFlavor.stringFlavor ) ) ;
}
catch ( UnsupportedFlavorException ex )
{
logger.error ( "Can not paste from clipboard" , ex ) ;
}
catch ( IOException ex )
{
logger.error ( "Can not paste from clipboard" , ex ) ;
}
}
}
public void handleRedo ( )
{
try
{
this.document.removeDocumentListener ( this.doclistener ) ;
this.undohistory.push ( this.document.getText ( 0 , this.document
.getLength ( ) ) ) ;
this.document.remove ( 0 , this.document.getLength ( ) ) ;
this.document.insertString ( 0 , this.redohistory.pop ( ) , null ) ;
setUndoStatus ( true ) ;
this.document.addDocumentListener ( this.doclistener ) ;
loadOutlineExpression ( Outline.ExecuteAutoChange.EDITOR ) ;
if ( this.redohistory.size ( ) == 0 )
{
setRedoStatus ( false ) ;
}
}
catch ( BadLocationException e )
{
logger.error ( "Cannot handle an undo" , e ) ;
}
}
public void handleUndo ( )
{
try
{
this.document.removeDocumentListener ( this.doclistener ) ;
String doctext = this.document.getText ( 0 , this.document.getLength ( ) ) ;
String historytext ;
if ( this.undohistory.peek ( ).equals ( this.initialContent ) )
{
historytext = this.undohistory.peek ( ) ;
setUndoStatus ( false ) ;
}
else
{
historytext = this.undohistory.pop ( ) ;
}
this.document.remove ( 0 , this.document.getLength ( ) ) ;
this.document.insertString ( 0 , historytext , null ) ;
this.redohistory.add ( doctext ) ;
setRedoStatus ( true ) ;
this.document.addDocumentListener ( this.doclistener ) ;
loadOutlineExpression ( Outline.ExecuteAutoChange.EDITOR ) ;
}
catch ( BadLocationException e )
{
logger.error ( "Cannot handle an undo" , e ) ;
}
}
private void initComponents ( Language language )
{
this.editor = new StyledLanguageEditor ( ) ;
this.document = new StyledLanguageDocument ( language ) ;
JPanel compoundPanel = new JPanel ( ) ;
compoundPanel.setLayout ( new BorderLayout ( ) ) ;
this.scrollpane = new JScrollPane ( ) ;
compoundPanel.add ( this.scrollpane , BorderLayout.CENTER ) ;
this.sideBar = new SideBar ( this.scrollpane , this.document , this.editor ) ;
this.sideBar.addSideBarListener ( new SideBarListener ( )
{
/**
* Marks the text with the given offsets.
*
* @param pLeft The left offset of the text which should be marked.
* @param pRight The right offset of the text which should be marked.
*/
@ SuppressWarnings ( "synthetic-access" )
public void markText ( int pLeft , int pRight )
{
if ( ( TextEditorPanel.this.editor.getSelectionStart ( ) == pLeft )
&& ( TextEditorPanel.this.editor.getSelectionEnd ( ) == pRight ) )
{
TextEditorPanel.this.removeSelectedText ( ) ;
}
else
{
TextEditorPanel.this.selectErrorText ( pLeft , pRight ) ;
}
}
/**
* Inserts a given text at the given index.
*
* @param pIndex The index in the text, where the text should be inserted.
* @param pInsertText The text which should be inserted.
*/
@ SuppressWarnings ( "synthetic-access" )
public void insertText ( int pIndex , String pInsertText )
{
int countSpaces = 0 ;
try
{
while ( TextEditorPanel.this.document.getText ( pIndex + countSpaces ,
1 ).equals ( " " ) ) //$NON-NLS-1$
{
countSpaces ++ ;
}
}
catch ( BadLocationException e )
{
// Do nothing
}
try
{
int offset = 0 ;
String text = pInsertText ;
if ( ( countSpaces >= 1 )
&& ( text.substring ( 0 , 1 ).equals ( " " ) ) ) //$NON-NLS-1$
{
text = text.substring ( 1 ) ;
offset ++ ;
countSpaces -- ;
}
if ( ( countSpaces >= 1 )
&& ( text.substring ( text.length ( ) - 1 ).equals ( " " ) ) ) //$NON-NLS-1$
{
text = text.substring ( 0 , text.length ( ) - 1 ) ;
}
TextEditorPanel.this.document.insertString ( pIndex + offset , text ,
null ) ;
}
catch ( BadLocationException e )
{
// Do nothing
}
}
/**
* Replaces the texts with the given start and end offsets with the
* replace text.
*
* @param pStart The start offsets of the texts which should be renamed.
* @param pEnd The end offsets of the texts which should be renamed.
* @param pReplaceText The replace text.
*/
@ SuppressWarnings ( "synthetic-access" )
public void replaceText ( int [ ] pStart , int [ ] pEnd ,
String pReplaceText )
{
int offset = 0 ;
for ( int i = 0 ; i < pStart.length ; i ++ )
{
try
{
int length = pEnd [ i ] - pStart [ i ] ;
TextEditorPanel.this.document.replace ( offset + pStart [ i ] ,
length , pReplaceText , null ) ;
offset += pReplaceText.length ( ) - length ;
}
catch ( BadLocationException e )
{
// Do nothing
}
}
}
} ) ;
compoundPanel.add ( this.sideBar , BorderLayout.WEST ) ;
// this.scrollpane.setHorizontalScrollBarPolicy (
// JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS ) ;
this.scrollpane.setViewportView ( this.editor ) ;
this.editor.setDocument ( this.document ) ;
this.editor.setAutoscrolls ( false ) ;
this.jSplitPane = new JSplitPane ( JSplitPane.VERTICAL_SPLIT ) ;
this.outline = new DefaultOutline ( this ) ;
JPanel jPanelOutline = this.outline.getPanel ( ) ;
this.jSplitPane.setLeftComponent ( compoundPanel ) ;
this.jSplitPane.setRightComponent ( jPanelOutline ) ;
this.jSplitPane.setOneTouchExpandable ( true ) ;
this.jSplitPane.setResizeWeight ( 0.5 ) ;
this.document.addDocumentListener ( this.doclistener ) ;
this.undohistory.push ( "" ) ;
// the popup menu and listeners
this.popup = new JPopupMenu ( ) ;
MenuListener menulistener = new MenuListener ( ) ;
this.undoItem = new JMenuItem ( java.util.ResourceBundle.getBundle (
"de/unisiegen/tpml/ui/ui" ).getString ( "Undo" ) ) ;
this.redoItem = new JMenuItem ( java.util.ResourceBundle.getBundle (
"de/unisiegen/tpml/ui/ui" ).getString ( "Redo" ) ) ;
JSeparator separator = new JSeparator ( ) ;
JMenuItem copyItem = new JMenuItem ( java.util.ResourceBundle.getBundle (
"de/unisiegen/tpml/ui/ui" ).getString ( "Copy" ) ) ;
JMenuItem cutItem = new JMenuItem ( java.util.ResourceBundle.getBundle (
"de/unisiegen/tpml/ui/ui" ).getString ( "Cut" ) ) ;
JMenuItem pasteItem = new JMenuItem ( java.util.ResourceBundle.getBundle (
"de/unisiegen/tpml/ui/ui" ).getString ( "Paste" ) ) ;
this.undoItem.addActionListener ( menulistener ) ;
this.undoItem.setEnabled ( false ) ;
this.undoItem.setIcon ( new javax.swing.ImageIcon ( getClass ( )
.getResource ( "/de/unisiegen/tpml/ui/icons/undo16.gif" ) ) ) ;
this.redoItem.addActionListener ( menulistener ) ;
this.redoItem.setEnabled ( false ) ;
this.redoItem.setIcon ( new javax.swing.ImageIcon ( getClass ( )
.getResource ( "/de/unisiegen/tpml/ui/icons/redo16.gif" ) ) ) ;
copyItem.addActionListener ( menulistener ) ;
copyItem.setIcon ( new javax.swing.ImageIcon ( getClass ( ).getResource (
"/de/unisiegen/tpml/ui/icons/copy16.gif" ) ) ) ;
cutItem.addActionListener ( menulistener ) ;
cutItem.setIcon ( new javax.swing.ImageIcon ( getClass ( ).getResource (
"/de/unisiegen/tpml/ui/icons/cut16.gif" ) ) ) ;
pasteItem.addActionListener ( menulistener ) ;
pasteItem.setIcon ( new javax.swing.ImageIcon ( getClass ( ).getResource (
"/de/unisiegen/tpml/ui/icons/paste16.gif" ) ) ) ;
this.popup.add ( this.undoItem ) ;
this.popup.add ( this.redoItem ) ;
this.popup.add ( separator ) ;
this.popup.add ( copyItem ) ;
this.popup.add ( cutItem ) ;
this.popup.add ( pasteItem ) ;
this.editor.addMouseListener ( new PopupListener ( ) ) ;
add ( this.jSplitPane , BorderLayout.CENTER ) ;
}
public void insertText ( String text )
{
try
{
this.document.insertString ( this.editor.getCaretPosition ( ) , text ,
null ) ;
}
catch ( BadLocationException e )
{
logger.error ( "Text could not be inserted into document" , e ) ;
}
}
// public void setChanged(boolean changeStatus) {
// firePropertyChange("changed", this.changed, changeStatus);
// this.changed = changeStatus;
// }
public boolean isChanged ( )
{
return this.changed ;
}
public boolean isNextStatus ( )
{
return this.nextStatus ;
}
/**
* {@inheritDoc} This method always returns false, because <i>Pong</i> cannot
* be played from the editor.
*
* @see de.unisiegen.tpml.ui.EditorComponent#isPongStatus()
*/
public boolean isPongStatus ( )
{
return false ;
}
public boolean isRedoStatus ( )
{
return this.redoStatus ;
}
public boolean isUndoStatus ( )
{
return this.undoStatus ;
}
/**
* Loads a new {@link Expression} into the {@link Outline}.
*
* @param pExecute Indicates who loads the new Expression.
*/
protected void loadOutlineExpression ( Outline.Execute pExecute )
{
try
{
this.outline.load ( this.document.getExpression ( ) , pExecute ) ;
}
catch ( Exception e )
{
this.outline.load ( null , pExecute ) ;
}
}
public void lostOwnership ( Clipboard arg0 , Transferable arg1 )
{
// we do not care so we do nothing
}
public void removeSelectedText ( )
{
int start = this.editor.getSelectionStart ( ) ;
int end = this.editor.getSelectionEnd ( ) ;
try
{
if ( start < end )
{
this.document.remove ( start , ( end - start ) ) ;
}
else
{
this.document.remove ( end , ( start - end ) ) ;
}
}
catch ( BadLocationException e )
{
logger.error ( "Cannot remove text from document" , e ) ;
}
}
private void selectErrorText ( int left , int right )
{
this.editor.select ( left , right ) ;
}
public void setAdvanced ( boolean status )
{
// the editor does not have an advanced mode so this is ignored.
}
public void setDefaultStates ( )
{
// setChanged(false);
setUndoStatus ( false ) ;
setRedoStatus ( false ) ;
setNextStatus ( false ) ;
}
private void setNextStatus ( boolean nextStatus )
{
if ( this.nextStatus != nextStatus )
{
boolean oldNextStatus = this.nextStatus ;
this.nextStatus = nextStatus ;
firePropertyChange ( "nextStatus" , oldNextStatus , nextStatus ) ;
}
}
private void setRedoStatus ( boolean redoStatus )
{
if ( this.redoStatus != redoStatus )
{
boolean oldRedoStatus = this.redoStatus ;
this.redoStatus = redoStatus ;
firePropertyChange ( "redoStatus" , oldRedoStatus , redoStatus ) ;
}
this.redoItem.setEnabled ( this.redoStatus ) ;
}
public void setText ( String text )
{
try
{
this.initialContent = text ;
this.currentContent = text ;
this.document.removeDocumentListener ( this.doclistener ) ;
this.document.remove ( 0 , this.document.getLength ( ) ) ;
this.document.insertString ( 0 , text , null ) ;
setRedoStatus ( false ) ;
this.redohistory.clear ( ) ;
setUndoStatus ( false ) ;
this.undohistory.clear ( ) ;
this.undohistory.push ( text ) ;
this.document.addDocumentListener ( this.doclistener ) ;
loadOutlineExpression ( Outline.ExecuteInit.EDITOR ) ;
}
catch ( BadLocationException e )
{
logger.error ( "Cannot set Text of the document" , e ) ;
}
}
private void setUndoStatus ( boolean undoStatus )
{
if ( this.undoStatus != undoStatus )
{
boolean oldUndoStatus = this.undoStatus ;
this.undoStatus = undoStatus ;
firePropertyChange ( "undoStatus" , oldUndoStatus , undoStatus ) ;
}
this.undoItem.setEnabled ( this.undoStatus ) ;
}
public JComponent getPrintPart ( )
{
return editor ;
}
}