package de.codesourcery.jasm16.ide.ui.views;
/**
* Copyright 2012 Tobias Gierke <tobias.gierke@code-sourcery.de>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.awt.Color;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Event;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.PopupMenu;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionAdapter;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.JTextPane;
import javax.swing.JToolBar;
import javax.swing.JViewport;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.ToolTipManager;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentEvent.EventType;
import javax.swing.event.DocumentListener;
import javax.swing.event.UndoableEditEvent;
import javax.swing.event.UndoableEditListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.text.AbstractDocument;
import javax.swing.text.AbstractDocument.DefaultDocumentEvent;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultHighlighter;
import javax.swing.text.DefaultStyledDocument.AttributeUndoableEdit;
import javax.swing.text.Document;
import javax.swing.text.DocumentFilter;
import javax.swing.text.Highlighter;
import javax.swing.text.JTextComponent;
import javax.swing.text.Position;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;
import javax.swing.text.View;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.UndoManager;
import javax.swing.undo.UndoableEdit;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import de.codesourcery.jasm16.ast.AST;
import de.codesourcery.jasm16.ast.ASTNode;
import de.codesourcery.jasm16.ast.CommentNode;
import de.codesourcery.jasm16.ast.EndMacroNode;
import de.codesourcery.jasm16.ast.IPreprocessorDirective;
import de.codesourcery.jasm16.ast.IncludeSourceFileNode;
import de.codesourcery.jasm16.ast.InstructionNode;
import de.codesourcery.jasm16.ast.InvokeMacroNode;
import de.codesourcery.jasm16.ast.LabelNode;
import de.codesourcery.jasm16.ast.RegisterReferenceNode;
import de.codesourcery.jasm16.ast.StartMacroNode;
import de.codesourcery.jasm16.ast.StatementNode;
import de.codesourcery.jasm16.ast.SymbolReferenceNode;
import de.codesourcery.jasm16.compiler.CompilationListener;
import de.codesourcery.jasm16.compiler.ICompilationError;
import de.codesourcery.jasm16.compiler.ICompilationUnit;
import de.codesourcery.jasm16.compiler.ISymbol;
import de.codesourcery.jasm16.compiler.ISymbolTable;
import de.codesourcery.jasm16.compiler.Severity;
import de.codesourcery.jasm16.compiler.SourceLocation;
import de.codesourcery.jasm16.compiler.io.AbstractResourceResolver;
import de.codesourcery.jasm16.compiler.io.DefaultResourceMatcher;
import de.codesourcery.jasm16.compiler.io.FileResource;
import de.codesourcery.jasm16.compiler.io.FileResourceResolver;
import de.codesourcery.jasm16.compiler.io.IResource;
import de.codesourcery.jasm16.compiler.io.IResource.ResourceType;
import de.codesourcery.jasm16.compiler.io.IResourceResolver;
import de.codesourcery.jasm16.compiler.phases.ExpandMacrosPhase;
import de.codesourcery.jasm16.exceptions.ResourceNotFoundException;
import de.codesourcery.jasm16.ide.IAssemblyProject;
import de.codesourcery.jasm16.ide.IWorkspace;
import de.codesourcery.jasm16.ide.IWorkspaceListener;
import de.codesourcery.jasm16.ide.NavigationHistory;
import de.codesourcery.jasm16.ide.NavigationHistory.INavigationHistoryListener;
import de.codesourcery.jasm16.ide.NavigationHistory.Location;
import de.codesourcery.jasm16.ide.WorkspaceListener;
import de.codesourcery.jasm16.ide.ui.utils.UIUtils;
import de.codesourcery.jasm16.ide.ui.viewcontainers.EditorContainer;
import de.codesourcery.jasm16.utils.ITextRegion;
import de.codesourcery.jasm16.utils.Line;
import de.codesourcery.jasm16.utils.Misc;
import de.codesourcery.jasm16.utils.TextRegion;
/**
* Abstract base-class for views that display source code.
*
* <p>This class provides common functionality like
* syntax-highlighing , navigation history and so forth.</p>
*
* @author tobias.gierke@code-sourcery.de
*/
public abstract class SourceCodeView extends AbstractView implements IEditorView {
private static final Logger LOG = Logger.getLogger(SourceCodeView.class);
// time to wait before recompiling after the user changed the source code
private static final int RECOMPILATION_DELAY_MILLIS = 300;
// UI widgets
private volatile JPanel panel;
private Object currentHighlight;
private boolean registeredWithTooltipManager = false;
private volatile boolean editable;
private SearchDialog searchDialog;
private Object currentUnderlineHighlight;
private final NavigationHistory navigationHistory;
private final PopupListener popupListener = new PopupListener();
private final UndoManager undoManager = new UndoManager();
private final UndoableEditListener undoListener = new UndoableEditListener()
{
public void undoableEditHappened(UndoableEditEvent e)
{
UndoableEdit edit = e.getEdit();
if ( edit instanceof AttributeUndoableEdit) {
return;
}
else if ( edit instanceof DefaultDocumentEvent) {
if ( ((DefaultDocumentEvent) edit).getType() == EventType.CHANGE ) {
return;
}
}
undoManager.addEdit(e.getEdit());
undoAction.updateUndoState();
redoAction.updateRedoState();
}
};
protected abstract class UndoRedoAction extends AbstractAction {
public void updateUndoState() {
if (undoManager.canUndo()) {
setEnabled(true);
putValue(Action.NAME, undoManager.getUndoPresentationName());
} else {
setEnabled(false);
putValue(Action.NAME, "Undo");
}
}
public void updateRedoState()
{
if (undoManager.canRedo()) {
setEnabled(true);
putValue(Action.NAME, undoManager.getRedoPresentationName() );
} else {
setEnabled(false);
putValue(Action.NAME, "Redo");
}
}
}
private final UndoRedoAction undoAction = new UndoRedoAction() {
@Override
public void actionPerformed(ActionEvent e) {
try {
undoManager.undo();
} catch (CannotUndoException ex) {
LOG.error("Unable to undo: " + ex,ex);
}
updateUndoState();
redoAction.updateRedoState();
}
};
private final UndoRedoAction redoAction = new UndoRedoAction() {
@Override
public void actionPerformed(ActionEvent e) {
try {
undoManager.redo();
} catch (CannotRedoException ex) {
LOG.error("Unable to redo: " + ex,ex);
}
updateRedoState();
undoAction.updateUndoState();
}
};
private final JTextField cursorPosition = new JTextField();
private final JTextPane editorPane = new JTextPane();
private volatile int documentListenerDisableCount = 0;
private JScrollPane editorScrollPane;
private final SimpleAttributeSet registerStyle;
private final SimpleAttributeSet commentStyle;
private final SimpleAttributeSet instructionStyle;
private final SimpleAttributeSet labelStyle;
private final SimpleAttributeSet preProcessorStyle;
private final SimpleAttributeSet errorStyle;
private final SimpleAttributeSet defaultStyle;
private final SimpleAttributeSet macroDefinitionStyle;
private final SimpleAttributeSet macroInvocationStyle;
// compiler
private final IResourceResolver resourceResolver;
protected final IWorkspace workspace;
private final INavigationHistoryListener navigationHistoryListener = new INavigationHistoryListener() {
@Override
public void navigationHistoryChanged()
{
UIUtils.invokeLater( new Runnable() {
@Override
public void run()
{
SourceCodeView.this.onNavigationHistoryChange();
}
} );
}
};
private volatile boolean navigationHistoryUpdatesEnabled = true; // controls whether the CaretListener will forward caret position changes to the NavigationHistory
private volatile boolean isBuilding = false;
private final IWorkspaceListener workspaceListener = new WorkspaceListener() {
public void projectDeleted(IAssemblyProject deletedProject)
{
if ( deletedProject.isSame( project ) )
{
dispose();
}
}
public void buildStarted(IAssemblyProject project)
{
if ( project.isSame( getCurrentProject() ) )
{
isBuilding = true;
}
}
public void buildFinished(IAssemblyProject project, boolean success) {
if ( project.isSame( getCurrentProject() ) ) {
isBuilding = false;
}
};
public void projectClosed(IAssemblyProject closedProject)
{
if ( closedProject.isSame( project ) )
{
dispose();
}
}
private void dispose() {
if ( getViewContainer() != null ) {
getViewContainer().disposeView( SourceCodeView.this );
} else {
SourceCodeView.this.dispose();
}
}
public void resourceDeleted(IAssemblyProject project, IResource deletedResource)
{
if ( DefaultResourceMatcher.INSTANCE.isSame( persistentResource , deletedResource ) )
{
dispose();
}
}
};
private IAssemblyProject project;
private String initialHashCode; // hash code used to check whether current editor content differs from the one on disk
private IResource persistentResource; // source code on disk
private InMemorySourceResource sourceInMemory; // possibly edited source code (in RAM / JEditorPane)
private ICompilationUnit compilationUnit;
private CompilationThread compilationThread = null;
protected static final class UnderlineHighlightPainter extends DefaultHighlighter.DefaultHighlightPainter {
private int thickness;
public UnderlineHighlightPainter(Color c, int thickness) {
super(c);
this.thickness = thickness;
}
@Override
public Shape paintLayer(Graphics g, int offs0, int offs1, Shape bounds,JTextComponent c, View view)
{
Rectangle r;
if (offs0 == view.getStartOffset() &&
offs1 == view.getEndOffset()) {
// Contained in view, can just use bounds.
if (bounds instanceof Rectangle) {
r = (Rectangle) bounds;
}
else {
r = bounds.getBounds();
}
}
else {
// Should only render part of View.
try {
// --- determine locations ---
Shape shape = view.modelToView(offs0, Position.Bias.Forward,
offs1,Position.Bias.Backward,
bounds);
r = (shape instanceof Rectangle) ?
(Rectangle)shape : shape.getBounds();
} catch (BadLocationException e) {
// can't render
r = null;
}
}
if (r != null)
{
Color color = getColor();
if (color == null) {
color = c.getSelectionColor();
}
g.setColor(color);
// If we are asked to highlight, we should draw something even
// if the model-to-view projection is of zero width (6340106).
r.width = Math.max(r.width, 1);
g.fillRect(r.x, r.y+r.height, r.width, thickness );
}
return r;
}
}
/* WAIT_FOR_EDIT-------> WAIT_FOR_TIMEOUT ------>( do compilation ) ----+
* ^ ^ | |
* | | | |
* | +---RESTART_TIMEOUT---+ |
* +-----------------------------------------------------------------+
*/
private enum WaitState {
WAIT_FOR_EDIT,
WAIT_FOR_TIMEOUT,
RESTART_TIMEOUT;
}
protected static final class StatusMessage
{
private final Severity severity;
private final ITextRegion location;
private final String message;
@SuppressWarnings("unused")
private final Throwable cause;
private final ICompilationError error;
public StatusMessage(Severity severity, String message)
{
this( severity , null , message , null ,null );
}
public StatusMessage(Severity severity, ITextRegion location, String message)
{
this( severity , location , message , null ,null);
}
public StatusMessage(Severity severity, ICompilationError error)
{
this(severity,error.getLocation(),error.getMessage(),error,error.getCause());
}
public StatusMessage(Severity severity, ITextRegion location, String message, ICompilationError error,Throwable cause)
{
if ( severity == null ) {
throw new IllegalArgumentException("severity must not be NULL.");
}
if (StringUtils.isBlank(message) ) {
throw new IllegalArgumentException("message must not be NULL/blank.");
}
this.severity = severity;
this.location = location;
this.message = message;
if ( cause == null ) {
this.cause = error != null ? error.getCause() : null;
} else {
this.cause = cause;
}
this.error = error;
}
public StatusMessage(Severity severity, String message, Throwable e)
{
this(severity, null , message , null , e );
}
public Severity getSeverity()
{
return severity;
}
public ITextRegion getLocation()
{
return location;
}
public String getMessage()
{
return message;
}
public ICompilationError getError()
{
return error;
}
}
protected final Highlighter getHighlighter() {
return editorPane.getHighlighter();
}
protected class StatusModel extends AbstractTableModel
{
private final List<StatusMessage> messages = new ArrayList<StatusMessage>();
private final int COL_SEVERITY = 0;
private final int COL_LOCATION = 1;
private final int COL_MESSAGE = 2;
public StatusModel() {
super();
}
@Override
public int getRowCount()
{
return messages.size();
}
public StatusMessage getMessage(int row) {
return messages.get(row);
}
public void addMessage(StatusMessage msg) {
if ( msg == null ) {
throw new IllegalArgumentException("msg must not be NULL.");
}
int index = messages.size();
messages.add( msg );
fireTableRowsInserted( index, index );
}
public void setMessage(StatusMessage msg)
{
if ( msg == null ) {
throw new IllegalArgumentException("msg must not be NULL.");
}
messages.clear();
messages.add( msg );
fireTableDataChanged();
}
@Override
public int getColumnCount()
{
return 3;
}
@Override
public String getColumnName(int columnIndex)
{
switch(columnIndex) {
case COL_SEVERITY:
return "Severity";
case COL_LOCATION:
return "Location";
case COL_MESSAGE:
return "Message";
default:
return "no column name?";
}
}
@Override
public Class<?> getColumnClass(int columnIndex)
{
return String.class;
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex)
{
return false;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex)
{
final StatusMessage msg = messages.get( rowIndex );
switch(columnIndex) {
case COL_SEVERITY:
return msg.getSeverity().toString();
case COL_LOCATION:
if ( msg.getLocation() != null ) {
SourceLocation location;
try {
location = getSourceLocation(msg.getLocation());
return "Line "+location.getLineNumber()+" , column "+location.getColumnNumber();
} catch (NoSuchElementException e) {
// ok, can't help it
}
}
return "<unknown>";
case COL_MESSAGE:
return msg.getMessage();
default:
return "no column name?";
}
}
@Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex)
{
throw new UnsupportedOperationException("");
}
public void addError(String message, IOException e1)
{
addMessage( new StatusMessage(Severity.ERROR , message , e1 ) );
}
public void addInfo(String message)
{
addMessage( new StatusMessage(Severity.INFO , message ) );
}
public void clearMessages()
{
messages.clear();
fireTableDataChanged();
}
}
protected final void replaceText(ITextRegion region,String newValue)
{
disableDocumentListener();
try {
editorPane.getDocument().remove( region.getStartingOffset() , region.getLength() );
editorPane.getDocument().insertString( region.getStartingOffset() , newValue , defaultStyle );
} catch (BadLocationException e) {
throw new RuntimeException(e);
} finally {
enableDocumentListener();
}
}
protected class CompilationThread extends Thread {
private final Object LOCK = new Object();
// @GuardedBy( LOCK )
private WaitState currentState = WaitState.WAIT_FOR_EDIT;
public CompilationThread() {
setDaemon( true );
}
@Override
public void run()
{
while( true )
{
try {
internalRun();
}
catch(Exception e) {
e.printStackTrace();
}
}
}
private void internalRun() throws InterruptedException, InvocationTargetException
{
synchronized( LOCK )
{
switch( currentState )
{
case WAIT_FOR_EDIT:
LOCK.wait();
return;
case RESTART_TIMEOUT:
currentState = WaitState.WAIT_FOR_TIMEOUT; // $FALL-THROUGH$
return;
case WAIT_FOR_TIMEOUT:
LOCK.wait( RECOMPILATION_DELAY_MILLIS );
if ( currentState != WaitState.WAIT_FOR_TIMEOUT ) {
return;
}
}
}
try {
SwingUtilities.invokeAndWait( new Runnable() {
@Override
public void run()
{
try {
validateSourceCode();
} catch (IOException e) {
e.printStackTrace();
} finally {
}
}
} );
} finally {
synchronized( LOCK )
{
currentState = WaitState.WAIT_FOR_EDIT;
}
}
}
public void documentChanged()
{
synchronized( LOCK )
{
currentState = WaitState.RESTART_TIMEOUT;
LOCK.notifyAll();
}
}
}
protected final void notifyDocumentChanged()
{
updateTitle();
if ( compilationThread == null )
{
compilationThread = new CompilationThread();
compilationThread.start();
}
compilationThread.documentChanged();
}
private final DocumentFilter documentFilter = new DocumentFilter()
{
private Line getLine(int offset)
{
try {
return compilationUnit.getLineForOffset( offset );
} catch(NoSuchElementException e) {
return null;
}
}
private int getIndentionOfPreviousLine(int currentOffset)
{
if ( compilationUnit == null || compilationUnit.getAST() == null ) {
return -1;
}
Line previous = null;
try {
previous = compilationUnit.getLineForOffset( currentOffset );
}
catch(NoSuchElementException e) {
return -1;
}
while ( previous != null )
{
StatementNode stmt = compilationUnit.getAST().getFirstStatementForOffset( previous.getLineStartingOffset() );
if ( stmt != null && stmt.hasChildren() ) {
return stmt.child(0).getTextRegion().getStartingOffset() - previous.getLineStartingOffset();
}
previous = compilationUnit.getPreviousLine( previous );
}
return -1;
}
public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException
{
if ( text.equals("\n") && length == 0 )
{
final int indention = getIndentionOfPreviousLine( offset );
if ( indention > 0 ) {
super.replace( fb , offset , length , text+StringUtils.repeat(" " , indention ), attrs );
return;
}
}
super.replace(fb, offset, length, text, attrs);
}
};
private final DocumentListener recompilationListener = new DocumentListener() {
private void textChanged(DocumentEvent e)
{
notifyDocumentChanged();
}
@Override
public void removeUpdate(DocumentEvent e) { textChanged(e); }
@Override
public void insertUpdate(DocumentEvent e) {
textChanged(e);
}
@Override
public void changedUpdate(DocumentEvent e) { /* do nothing, style change only */ }
};
private final CaretListener listener = new CaretListener() {
@Override
public void caretUpdate(final CaretEvent e)
{
// according to JDK docs, caretUpdate() is NOT necessarily called by the EDT,
// wrap all code so we can safely update UI components
final Runnable r = new Runnable() {
public void run()
{
// gotoLocation() will set updateNavigationHistory == false when it's been
// programatically triggered
if ( navigationHistoryUpdatesEnabled && sourceInMemory != null )
{
navigationHistory.add( new Location( project , sourceInMemory , e.getDot() ) );
}
// do not fire caret updates while building, this
// causes at least the SourceEditorView to access the AST that
// is still under construction and trigger a NPE in ASTNode#getNodeInRange()
if ( ! isEditable() || isBuilding() )
{
return;
}
if ( compilationUnit != null && compilationUnit.getAST() != null && compilationUnit.getAST().getTextRegion() != null )
{
try {
final SourceLocation location = getSourceLocation( e.getDot() );
cursorPosition.setHorizontalAlignment( JTextField.RIGHT );
cursorPosition.setText( "Line "+location.getLineNumber()+" , column "+location.getColumnNumber()+" (offset "+e.getDot()+")");
}
catch(NoSuchElementException e2) {
// ok, user clicked on unknown location
}
}
onCaretUpdate( e );
};
};
UIUtils.invokeLater( r );
}
};
protected void onCaretUpdate(CaretEvent e) {
}
public SourceCodeView(IResourceResolver resourceResolver , IWorkspace workspace,NavigationHistory navigationHistory,boolean isEditable)
{
if (workspace == null) {
throw new IllegalArgumentException("workspace must not be null");
}
if ( resourceResolver == null ) {
throw new IllegalArgumentException("resourceResolver must not be NULL.");
}
this.navigationHistory = navigationHistory;
this.resourceResolver = resourceResolver;
this.editable = isEditable;
this.workspace = workspace;
defaultStyle = new SimpleAttributeSet();
errorStyle = createStyle( Color.RED );
registerStyle = createStyle( Color.ORANGE );
commentStyle = createStyle( Color.WHITE );
macroDefinitionStyle = createStyle(Color.YELLOW );
macroInvocationStyle = createStyle(Color.YELLOW);
instructionStyle = createStyle( new Color(50,186,223) );
labelStyle = createStyle( new Color(237,237,81) );
preProcessorStyle = createStyle( new Color( 200 , 200 , 200 ) );
workspace.addWorkspaceListener( workspaceListener );
navigationHistory.addListener( navigationHistoryListener );
}
protected final static SimpleAttributeSet createStyle(Color color)
{
SimpleAttributeSet result = new SimpleAttributeSet();
StyleConstants.setForeground( result , color );
return result;
}
protected final SourceLocation getSourceLocation(ITextRegion range) {
return getSourceLocation( range.getStartingOffset() );
}
protected final SourceLocation getSourceLocation(int offset) {
final Line line = compilationUnit.getLineForOffset( offset);
return new SourceLocation( compilationUnit , line , new TextRegion( offset , 0 ) );
}
protected final GridBagConstraints constraints(int x,int y,int fill) {
GridBagConstraints result = new GridBagConstraints();
result.fill=fill;
result.weightx=1.0;
result.weighty=1.0;
result.gridheight=1;
result.gridwidth=1;
result.gridx=x;
result.gridy=y;
result.insets = new Insets(1,1,1,1);
return result;
}
protected void setStatusMessage(String message) {
}
protected final String getTextFromTextPane()
{
final int len = editorPane.getDocument().getLength();
if ( len == 0 ) {
return "";
}
try {
return editorPane.getDocument().getText( 0 , len );
} catch (BadLocationException e) {
throw new RuntimeException("bad location: ",e);
}
}
@Override
public final void openResource(IAssemblyProject project, IResource resource,int caretPosition) throws IOException
{
if ( this.project != project || this.persistentResource != resource ) {
openResource( project , resource , caretPosition,true );
}
}
protected final void openResource(final IAssemblyProject project, final IResource sourceFile,boolean compileSource) throws IOException
{
openResource(project,sourceFile,0,compileSource);
}
protected final void openResource(final IAssemblyProject project,
final IResource sourceFile,int caretPosition,boolean compileSource) throws IOException
{
if ( project == null ) {
throw new IllegalArgumentException("project must not be NULL");
}
if (sourceFile == null) {
throw new IllegalArgumentException("sourceFile must not be NULL");
}
// read source first so we don't discard internal state
// and end up with an IOException later on...
final String source = Misc.readSource( sourceFile );
this.initialHashCode = Misc.calcHash( source );
this.project = project;
if ( sourceFile instanceof InMemorySourceResource) {
this.sourceInMemory = (InMemorySourceResource) sourceFile;
this.persistentResource = sourceInMemory.getPersistentResource();
} else {
this.sourceInMemory = new InMemorySourceResource( sourceFile , editorPane ) {
@Override
public String toString()
{
return "SourceCodeView[ "+persistentResource+" ]";
}
};
this.persistentResource = sourceFile;
}
clearHighlight();
try {
disableDocumentListener();
try
{
final Document doc = editorPane.getDocument();
doc.putProperty(Document.StreamDescriptionProperty, null);
disableNavigationHistoryUpdates();
System.out.println("Text length: "+( source == null ? 0 : source.length() ) );
try {
editorPane.setText( source );
} finally {
enableNavigationHistoryUpdates();
}
try {
editorPane.setCaretPosition( caretPosition );
} catch(IllegalArgumentException e) {
LOG.error("openResource(): Invalid caret position "+caretPosition+" in resource "+sourceFile);
}
if ( panel != null )
{
ICompilationUnit existing = null;
if ( ! compileSource ) {
existing = project.getProjectBuilder().getCompilationUnit( sourceFile );
}
validateSourceCode( existing );
}
} finally {
enableDocumentListener();
}
} finally {
enableNavigationHistoryUpdates();
}
editorPane.requestFocus();
updateTitle();
}
protected final boolean isBuilding() {
return isBuilding;
}
protected final void validateSourceCode() throws IOException {
validateSourceCode(null);
}
protected final void validateSourceCode(ICompilationUnit existing) throws IOException {
long time = -System.currentTimeMillis();
disableDocumentListener();
try
{
clearCompilationErrors();
onSourceCodeValidation();
final IResourceResolver delegatingResolver = new AbstractResourceResolver() {
private IResourceResolver getChildResourceResolver(IResource parent)
{
IResource r = parent == null ? getCurrentResource() : parent;
if ( ! ( r instanceof FileResource ) )
{
if ( r instanceof InMemorySourceResource) {
r = ((InMemorySourceResource) r).getPersistentResource();
}
}
if ( ! ( r instanceof FileResource ) ) {
throw new RuntimeException("Internal error, not a file-resource: "+getCurrentResource());
}
final FileResource fr = (FileResource) r;
return new FileResourceResolver( fr.getAbsoluteFile().getParentFile() ){
@Override
protected ResourceType determineResourceType(File file)
{
// TODO: Maybe implement some more general mechanism of determining resource types ?
return project.getConfiguration().isSourceFile( file ) ? ResourceType.SOURCE_CODE : ResourceType.UNKNOWN;
}
};
}
@Override
public IResource resolve(String identifier) throws ResourceNotFoundException
{
try {
if ( resourceResolver != null ) {
return resourceResolver.resolve( identifier );
}
}
catch(ResourceNotFoundException e)
{
}
return getChildResourceResolver(null).resolve( identifier );
}
@Override
public IResource resolveRelative(String identifier, IResource parent)
throws ResourceNotFoundException
{
try {
if ( resourceResolver != null ) {
return resourceResolver.resolveRelative( identifier , parent );
}
}
catch(ResourceNotFoundException e)
{
}
final IResource realParent;
if ( parent instanceof InMemorySourceResource )
{
realParent = ((InMemorySourceResource ) parent).getPersistentResource();
} else {
realParent = parent;
}
return getChildResourceResolver( parent ).resolveRelative( identifier , realParent );
}
};
try
{
if ( existing == null || existing.getAST() == null )
{
compilationUnit = project.getProjectBuilder().parse(
sourceInMemory ,
delegatingResolver,
new CompilationListener()
);
} else {
compilationUnit = existing;
}
}
catch(Exception e) {
LOG.error("validateSourceCode(): ",e);
}
finally {
doHighlighting( compilationUnit , true );
}
for ( ICompilationError error : compilationUnit.getErrors() )
{
onCompilationError( error );
}
for ( ICompilationError error : compilationUnit.getWarnings() )
{
onCompilationWarning( error );
}
} finally {
enableDocumentListener();
time += System.currentTimeMillis();
System.out.println("Source code validation: "+time+" ms");
}
}
protected void onSourceCodeValidation() {
}
protected final void doHighlighting(ICompilationUnit unit,boolean called)
{
if ( unit == null ) {
throw new IllegalArgumentException("Internal error,compilation unit must not be NULL.");
}
if ( panel == null ) {
return;
}
if ( unit.getAST() != null )
{
onHighlightingStart();
long time = -System.currentTimeMillis();
try
{
final int markerCount = unit.getMarkers( (String[]) null ).size();
System.out.println("DEBUG: Starting to highlight "+unit.getResource()+" with "+markerCount+" markers.");
doSemanticHighlighting( unit );
} finally {
time += System.currentTimeMillis();
System.out.println("DEBUG: Highlighting "+unit.getResource()+" took "+time+" ms.");
}
}
if ( unit.hasErrors() )
{
highlightCompilationErrors( compilationUnit );
}
}
protected void onHighlightingStart() {
}
protected final void doSemanticHighlighting(ICompilationUnit unit)
{
if ( unit.getAST() == null ) {
return;
}
// changing character styles triggers
// change events that in turn would
// again trigger recompilation...we don't want that...
disableDocumentListener();
try {
final ITextRegion visible = getVisibleTextRegion();
if ( visible != null ) {
final List<ASTNode> nodes = unit.getAST().getNodesInRange( visible );
System.out.println("Highlighting "+nodes.size()+" nodes.");
for ( ASTNode child : nodes )
{
doSemanticHighlighting( unit , child );
}
}
} finally {
enableDocumentListener();
}
}
protected final void doSemanticHighlighting(ICompilationUnit unit, ASTNode node)
{
if ( highlight( node ) ) {
return; // don't highlight children if parent already was
}
if ( ! (node instanceof IncludeSourceFileNode ) ) {
for ( ASTNode child : node.getChildren() ) {
doSemanticHighlighting( unit , child );
}
}
}
protected final boolean highlight(ASTNode node)
{
if ( node instanceof StartMacroNode || node instanceof EndMacroNode)
{
highlight( node , macroDefinitionStyle );
return true;
}
else if ( node instanceof InvokeMacroNode)
{
highlight( node , macroInvocationStyle );
return true;
}
else if ( node instanceof InstructionNode )
{
ITextRegion children = null;
for ( ASTNode child : node.getChildren() )
{
if ( children == null ) {
children = child.getTextRegion();
} else {
children.merge( child.getTextRegion() );
}
}
ITextRegion whole = new TextRegion( node.getTextRegion() );
whole.subtract( children );
highlight( whole , instructionStyle );
return true;
}
else if ( node instanceof IPreprocessorDirective)
{
highlight( node , preProcessorStyle );
return true;
}
else if ( node instanceof SymbolReferenceNode || node instanceof LabelNode )
{
highlight( node , labelStyle );
return true;
} else if ( node instanceof CommentNode ) {
highlight( node , commentStyle );
return true;
} else if ( node instanceof RegisterReferenceNode ) {
highlight( node , registerStyle );
return true;
}
return false;
}
protected final void highlight(ASTNode node, AttributeSet attributes)
{
highlight( node.getTextRegion() , attributes );
}
protected final void highlight(ITextRegion range, AttributeSet attributes)
{
editorPane.getStyledDocument().setCharacterAttributes( range.getStartingOffset() , range.getLength() , attributes , true );
}
public final void moveCursorTo(ITextRegion location,boolean updateNavigationHistory)
{
moveCursorTo( location.getStartingOffset() , updateNavigationHistory );
}
public final void moveCursorTo(int offset,boolean updateNavigationHistory)
{
if ( compilationUnit == null || compilationUnit.getAST() == null ) {
return;
}
if ( ! editorPane.hasFocus() ) {
editorPane.requestFocus();
}
try
{
if ( ! updateNavigationHistory ) {
disableNavigationHistoryUpdates();
try {
editorPane.setCaretPosition( offset );
} finally {
enableNavigationHistoryUpdates();
}
} else {
editorPane.setCaretPosition( offset );
}
}
catch(IllegalArgumentException e) {
LOG.error("moveCursorTo(): Failed to offset "+offset+" on project "+project+" , resource "+sourceInMemory,e);
}
centerCurrentLineInScrollPane();
}
public final void centerCurrentLineInScrollPane()
{
final Runnable r = new Runnable() {
@Override
public void run() {
final Container container = SwingUtilities.getAncestorOfClass(JViewport.class, editorPane);
if (container == null) {
return;
}
try {
final Rectangle r = editorPane.modelToView(editorPane.getCaretPosition());
if (r == null ) {
return;
}
final JViewport viewport = (JViewport) container;
final int extentHeight = viewport.getExtentSize().height;
final int viewHeight = viewport.getViewSize().height;
int y = Math.max(0, r.y - (extentHeight / 2));
y = Math.min(y, viewHeight - extentHeight);
viewport.setViewPosition(new Point(0, y));
}
catch (BadLocationException ble) {
LOG.error("centerCurrentLineInScrollPane(): ",ble);
}
}
};
UIUtils.invokeLater( r );
}
protected final boolean canNavigationHistoryBack()
{
return navigationHistory.canGoBack();
}
protected final boolean canNavigationHistoryForward() {
return navigationHistory.canGoForward();
}
protected final void navigationHistoryBack()
{
gotoLocation( navigationHistory.goBack() );
}
protected final void navigationHistoryForward()
{
gotoLocation( navigationHistory.goForward() );
}
// guaranteed to only be called on EDT
protected void onNavigationHistoryChange() {
}
public final void gotoLocation(final int offset) {
gotoLocation(project,sourceInMemory , offset,false);
}
private final void gotoLocation(Location location)
{
if ( location == null ) {
return;
}
IAssemblyProject project = workspace.getProjectForResource( location.getResource() );
gotoLocation(project,location.getResource(),location.getOffset(),true);
}
private final void gotoLocation(
IAssemblyProject project,
IResource resource,
final int offset,
final boolean triggeredProgramatically)
{
if ( this.project != project || ! isCurrentlyDisplayed( resource ) )
{
if ( getViewContainer() instanceof EditorContainer)
{
try {
((EditorContainer) getViewContainer()).openResource( workspace , project , resource , offset );
} catch (IOException e) {
LOG.error("gotoLocation(): Failed for project "+project+", resource "+resource,e);
}
return;
}
try {
openResource( project , resource , offset , true );
}
catch (IOException e) {
LOG.error("gotoLocation(): Failed to project "+project+",resource "+resource,e);
}
return;
}
final Runnable r = new Runnable()
{
@Override
public void run()
{
editorPane.requestFocusInWindow();
if ( triggeredProgramatically )
{
disableNavigationHistoryUpdates();
editorPane.setCaretPosition( offset );
enableNavigationHistoryUpdates();
}
else
{
editorPane.setCaretPosition( offset );
}
centerCurrentLineInScrollPane();
}
};
UIUtils.invokeLater( r );
}
protected final boolean isCurrentlyDisplayed(IResource resource)
{
if ( this.sourceInMemory == null ) {
return false;
}
return this.sourceInMemory.getIdentifier().equals( resource.getIdentifier() );
}
protected final void disableNavigationHistoryUpdates() {
navigationHistoryUpdatesEnabled = false;
}
protected final void enableNavigationHistoryUpdates() {
navigationHistoryUpdatesEnabled = true;
}
protected final ITextRegion getVisibleTextRegion()
{
JViewport viewport = editorScrollPane.getViewport();
Rectangle viewRect = viewport.getViewRect();
Point p1 = viewRect.getLocation();
int startIndex = editorPane.viewToModel(p1);
if ( startIndex < 0 ) {
return null;
}
Point p2 = new Point(p1.x + viewRect.width-10 , p1.y + viewRect.height-10 ); // -10 is some arbitrary offset to fix an issue with viewToModel() returning a position at the end of the input text
int endIndex = editorPane.viewToModel(p2);
if ( endIndex < 0 ) {
return null;
}
int len = endIndex-startIndex;
if ( len < 0) {
return null;
}
System.out.println("getVisibleTextRegion( "+p1+" , "+p2+" => "+startIndex+","+endIndex);
return new TextRegion( startIndex , len );
}
// protected final ITextRegion getVisibleTextRegion()
// {
// final Point startPoint = editorScrollPane.getViewport().getViewPosition();
// final Dimension size = editorScrollPane.getViewport().getExtentSize();
//
// final Point endPoint = new Point(startPoint.x + size.width, startPoint.y + size.height);
// try {
// final int start = editorPane.viewToModel( startPoint );
// if ( start < 0 ) {
// return null;
// }
// final int end = editorPane.viewToModel( endPoint );
// if ( end < 0 ) {
// return null;
// }
// final int len = end-start;
// if ( len < 0 ) {
// return null;
// }
// return new TextRegion( start , len );
// }
// catch(NullPointerException e)
// {
// LOG.error("getVisibleTextRegion(): Caught ",e);
// return null;
// }
// }
protected final void clearCompilationErrors()
{
disableDocumentListener();
try {
final StyledDocument doc = editorPane.getStyledDocument();
doc.setCharacterAttributes( 0 , doc.getLength() , defaultStyle , true );
} finally {
enableDocumentListener();
}
}
protected final void disableDocumentListener()
{
documentListenerDisableCount++;
editorPane.getDocument().removeDocumentListener( recompilationListener );
if ( editorPane.getDocument() instanceof AbstractDocument) {
((AbstractDocument) editorPane.getDocument()).setDocumentFilter( null );
}
}
protected final void enableDocumentListener()
{
documentListenerDisableCount--;
if ( documentListenerDisableCount == 0)
{
editorPane.getDocument().addDocumentListener( recompilationListener );
if ( editorPane.getDocument() instanceof AbstractDocument) {
((AbstractDocument) editorPane.getDocument()).setDocumentFilter( documentFilter );
}
}
}
protected final void highlightCompilationErrors(ICompilationUnit unit)
{
disableDocumentListener();
try
{
for ( ICompilationError error : unit.getErrors() )
{
final ITextRegion location;
if ( error.getLocation() != null ) {
location = error.getLocation();
}
else
{
if ( error.getErrorOffset() != -1 ) {
location = new TextRegion( error.getErrorOffset(), 1 );
} else {
location = null;
}
}
if ( location != null )
{
highlight( location , errorStyle );
}
}
} finally {
enableDocumentListener();
}
}
protected void onCompilationError(ICompilationError error) {
}
protected void onCompilationWarning(ICompilationError error) {
}
// ============= view creation ===================
@Override
public JPanel getPanel()
{
if ( panel == null ) {
panel = createPanel();
if ( this.persistentResource != null )
{
try {
validateSourceCode();
} catch (IOException e) {
LOG.error("getPanel(): ",e);
}
}
}
return panel;
}
private final MouseListener mouseListener = new MouseAdapter() {
public void mouseClicked(java.awt.event.MouseEvent e) {
if ( e.getClickCount() == 1 && e.getButton() == MouseEvent.BUTTON1 && ( e.getModifiersEx() & MouseEvent.CTRL_DOWN_MASK ) != 0 )
{
// navigate to symbol definition
final ASTNode node = getASTNodeForLocation( e.getPoint() );
if ( node instanceof SymbolReferenceNode)
{
final SymbolReferenceNode ref = (SymbolReferenceNode) node;
gotoToSymbolDefinition( ref );
}
}
}
};
protected final void gotoToSymbolDefinition(SymbolReferenceNode ref)
{
if ( getCurrentCompilationUnit() != null )
{
ISymbolTable table = getCurrentCompilationUnit().getSymbolTable();
ISymbol symbol = ref.resolve( table , true );
if ( symbol != null )
{
if ( symbol != null )
{
final ITextRegion location = symbol.getLocation();
final ICompilationUnit newCompilationUnit = symbol.getCompilationUnit();
final IAssemblyProject project = workspace.getProjectForResource( newCompilationUnit.getResource() );
gotoLocation( project , newCompilationUnit.getResource() , location.getStartingOffset() , false );
}
}
}
}
private final JPanel createPanel()
{
disableDocumentListener(); // necessary because setting colors on editor pane triggers document change listeners (is considered a style change...)
try {
editorPane.setEditable( editable );
editorPane.getDocument().addUndoableEditListener( undoListener );
editorPane.setCaretColor( Color.WHITE );
setupKeyBindings( editorPane );
setColors( editorPane );
editorScrollPane = new JScrollPane(editorPane);
setColors( editorScrollPane );
editorPane.addCaretListener( listener );
editorPane.addMouseListener( mouseListener );
editorPane.addMouseMotionListener( new MouseMotionAdapter() {
@Override
public void mouseMoved(MouseEvent e)
{
if ( (e.getModifiersEx() & MouseEvent.CTRL_DOWN_MASK) != 0 ) // ctrl pressed
{
final ASTNode node = getASTNodeForLocation( e.getPoint() );
if ( node instanceof SymbolReferenceNode) {
maybeUnderlineIdentifierAt( e.getPoint() );
} else {
clearUnderlineHighlight();
}
}
else if ( compilationUnit != null )
{
String tooltipText=null;
if ( compilationUnit != null )
{
final ASTNode node = getASTNodeForLocation( e.getPoint() );
if ( node instanceof InvokeMacroNode)
{
tooltipText = ExpandMacrosPhase.expandInvocation( (InvokeMacroNode) node , compilationUnit );
if ( tooltipText != null ) {
tooltipText = "<html>"+tooltipText.replace("\n","<br>")+"</html>";
}
}
}
if ( ! StringUtils.equals( editorPane.getToolTipText() , tooltipText ) ) {
editorPane.setToolTipText( tooltipText );
}
}
}
});
editorPane.addMouseListener( popupListener );
} finally {
enableDocumentListener();
}
EditorContainer.addEditorCloseKeyListener( editorPane , this );
editorScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
editorScrollPane.setPreferredSize( new Dimension(400,600 ) );
editorScrollPane.setMinimumSize(new Dimension(100, 100));
final AdjustmentListener adjustmentListener = new AdjustmentListener() {
@Override
public void adjustmentValueChanged(AdjustmentEvent e)
{
if ( ! e.getValueIsAdjusting() )
{
if ( compilationUnit != null ) {
doHighlighting( compilationUnit , false );
}
}
}
};
editorScrollPane.getVerticalScrollBar().addAdjustmentListener( adjustmentListener );
editorScrollPane.getHorizontalScrollBar().addAdjustmentListener( adjustmentListener );
// button panel
final JPanel topPanel = new JPanel();
final JToolBar toolbar = new JToolBar();
setColors( toolbar );
cursorPosition.setSize( new Dimension(400,15) );
cursorPosition.setEditable( false );
setColors( cursorPosition );
/**
* TOOLBAR
* SOURCE
* cursor position
* status area
*/
topPanel.setLayout( new GridBagLayout() );
GridBagConstraints cnstrs = constraints( 0, 0 , GridBagConstraints.HORIZONTAL );
cnstrs.gridwidth = GridBagConstraints.REMAINDER;
cnstrs.weighty = 0;
topPanel.add( toolbar , cnstrs );
cnstrs = constraints( 0, 1 , GridBagConstraints.BOTH );
cnstrs.gridwidth = GridBagConstraints.REMAINDER;
topPanel.add( editorScrollPane , cnstrs );
cnstrs = constraints( 0, 2 , GridBagConstraints.HORIZONTAL);
cnstrs.gridwidth = GridBagConstraints.REMAINDER;
cnstrs.weighty = 0;
topPanel.add( cursorPosition , cnstrs );
cnstrs = constraints( 0, 3 , GridBagConstraints.HORIZONTAL);
cnstrs.gridwidth = GridBagConstraints.REMAINDER;
cnstrs.weighty = 0;
// setup result panel
final JPanel panel = new JPanel();
panel.setLayout( new GridBagLayout() );
setColors( panel );
cnstrs = constraints( 0 , 0 , true , true , GridBagConstraints.BOTH );
panel.add( topPanel , cnstrs );
return panel;
}
/**
* Returns the mouse pointer's location relative to the
* editorpane or <code>null</code> if the mouse pointer is
* outside of the editor.
*
* @return location or <code>null</code> if mouse ptr is outside of the editor pane
*/
protected final Point getMouseLocation()
{
final Point location = MouseInfo.getPointerInfo().getLocation() ;
final Point locOnScreen = editorPane.getLocationOnScreen();
final Point result= new Point( location.x - locOnScreen.x , location.y - locOnScreen.y );
return editorPane.contains( result ) ? result : null;
}
protected final void setupKeyBindings(final JTextPane editor)
{
// 'Save' action
addKeyBinding( editor ,
KeyStroke.getKeyStroke(KeyEvent.VK_S,Event.CTRL_MASK),
new AbstractAction()
{
@Override
public void actionPerformed(ActionEvent e) {
saveCurrentFile();
}
});
// 'Underline text when pressing CTRL while hovering over an identifier'
editorPane.addKeyListener( new KeyAdapter()
{
private boolean isControlKey(KeyEvent e) {
return e.getKeyCode() == KeyEvent.VK_CONTROL;
}
public void keyPressed(KeyEvent e)
{
if ( isControlKey(e) )
{
final Point ptr = getMouseLocation();
if ( ptr != null ) {
maybeUnderlineIdentifierAt( ptr );
}
}
}
public void keyReleased(KeyEvent e) {
if ( isControlKey(e) ) {
clearUnderlineHighlight();
}
};
} );
// "Undo" action
addKeyBinding( editor ,
KeyStroke.getKeyStroke(KeyEvent.VK_Z,Event.CTRL_MASK),
undoAction );
addKeyBinding( editor ,
KeyStroke.getKeyStroke(KeyEvent.VK_Y,Event.CTRL_MASK),
redoAction );
// 'Search' action
addKeyBinding( editor ,
KeyStroke.getKeyStroke(KeyEvent.VK_F,Event.CTRL_MASK),
new AbstractAction()
{
@Override
public void actionPerformed(ActionEvent e) {
showSearchDialog();
}
});
setupKeyBindingsHook( editor );
}
protected void setupKeyBindingsHook(JTextPane editor) {
}
protected final void saveCurrentFile() {
if ( ! hasUnsavedContent() ) {
return;
}
final String source = getTextFromTextPane();
try {
Misc.writeResource( getCurrentResource() , source );
this.initialHashCode = Misc.calcHash( source );
updateTitle();
} catch (IOException e1) {
LOG.error("save(): Failed to write to "+getCurrentResource());
return;
}
if ( compilationUnit == null || compilationUnit.hasErrors() ) {
return;
}
try {
getCurrentProject().getProjectBuilder().build();
}
catch (IOException e) {
LOG.error("save(): Compilation failed",e);
}
}
public final IAssemblyProject getCurrentProject() {
return project;
}
public final IResource getCurrentResource() {
return this.persistentResource;
}
public final IResource getSourceFromMemory() {
return this.sourceInMemory;
}
@Override
public final void disposeHook()
{
try {
disposeHook2();
}
finally
{
if ( registeredWithTooltipManager ) {
ToolTipManager.sharedInstance().unregisterComponent( editorPane );
}
workspace.removeWorkspaceListener( workspaceListener );
navigationHistory.removeListener( navigationHistoryListener );
}
}
protected void disposeHook2() {
}
@Override
public final void refreshDisplay()
{
try {
if ( project != null ) {
validateSourceCode();
}
}
catch (IOException e)
{
e.printStackTrace();
}
refreshDisplayHook();
}
protected void refreshDisplayHook() {
}
@Override
public String getTitle()
{
if ( getCurrentResource() == null ) {
return "source view";
}
final String prefix = hasUnsavedContent() ? "*" : "";
final String identifier;
if ( getCurrentResource() instanceof FileResource ) {
identifier = ((FileResource) getCurrentResource()).getFile().getName();
} else {
identifier = getCurrentResource().getIdentifier();
}
return prefix + identifier;
}
@Override
public final boolean hasUnsavedContent()
{
if ( this.persistentResource == null ) {
return false;
}
return ! initialHashCode.equals( Misc.calcHash( getTextFromTextPane() ) );
}
@Override
public final boolean mayBeDisposed() {
return ! hasUnsavedContent();
}
protected final void updateTitle()
{
final String title = ( hasUnsavedContent() ? "*" : "")+getCurrentResource().getIdentifier();
getViewContainer().setTitle( SourceCodeView.this , title );
}
@Override
public String getID() {
return "source-view";
}
public final boolean isEditable()
{
return editable;
}
public void setEditable(boolean editable)
{
this.editable = editable;
editorPane.setEditable( editable );
}
protected final ICompilationUnit getCurrentCompilationUnit() {
return compilationUnit;
}
protected final void addMouseListener(MouseAdapter listener) {
editorPane.addMouseListener( listener );
editorPane.addMouseMotionListener( listener );
}
protected final void showTooltip(String s)
{
if ( ! registeredWithTooltipManager ) {
ToolTipManager.sharedInstance().registerComponent( editorPane );
registeredWithTooltipManager = true;
}
editorPane.setToolTipText( s );
}
protected final void clearTooltip() {
editorPane.setToolTipText( null );
}
protected final void removeMouseListener(MouseAdapter listener) {
editorPane.removeMouseListener( listener );
editorPane.removeMouseMotionListener( listener );
}
protected final int getModelOffsetForLocation(Point p) {
return editorPane.viewToModel( p );
}
protected final void addPopupMenu(PopupMenu menu) {
editorPane.add( menu );
}
protected final ASTNode getASTNodeForLocation(Point p)
{
final AST ast = getCurrentCompilationUnit() != null ? getCurrentCompilationUnit().getAST() : null;
if ( ast == null ) {
return null;
}
int offset = editorPane.viewToModel( p );
if ( offset != -1 ) {
return ast.getNodeInRange( offset );
}
return null;
}
protected class PopupListener extends MouseAdapter
{
public void mousePressed(MouseEvent e) {
maybeShowPopup(e);
}
public void mouseReleased(MouseEvent e) {
maybeShowPopup(e);
}
private void maybeShowPopup(MouseEvent e)
{
if (e.isPopupTrigger())
{
final ASTNode node = getASTNodeForLocation( e.getPoint() );
final JPopupMenu menu = createPopupMenu( node , editorPane.getCaretPosition(),
editorPane.getSelectedText() );
if ( menu != null ) {
menu.show(e.getComponent(),e.getX(), e.getY());
}
}
}
}
protected JPopupMenu createPopupMenu(ASTNode node, int caretPosition,String currentSelection) {
return null;
}
protected final void showSearchDialog()
{
final int cursorPos = editorPane.getCaretPosition();
final String selection = editorPane.getSelectedText();
if ( searchDialog == null ) {
searchDialog = new SearchDialog();
searchDialog.setVisible( true );
searchDialog.activate(selection,cursorPos);
} else {
searchDialog.activate(selection,cursorPos);
}
}
protected final void highlightLocation(ITextRegion region) {
if (region == null) {
throw new IllegalArgumentException("region must not be null");
}
try {
if ( currentHighlight == null ) {
currentHighlight = editorPane.getHighlighter().addHighlight(
region.getStartingOffset() ,
region.getEndOffset() ,
new DefaultHighlighter.DefaultHighlightPainter(Color.WHITE) );
} else {
editorPane.getHighlighter().changeHighlight(
currentHighlight,
region.getStartingOffset() ,
region.getEndOffset() );
}
} catch (BadLocationException e) {
LOG.error("highlightLocation(): Bad location "+region,e);
throw new RuntimeException("Bad text location "+region,e);
}
}
protected final void clearUnderlineHighlight()
{
final Runnable r = new Runnable() {
@Override
public void run()
{
if ( currentUnderlineHighlight != null )
{
editorPane.getHighlighter().removeHighlight( currentUnderlineHighlight );
currentUnderlineHighlight = null;
editorPane.setCursor( Cursor.getPredefinedCursor( Cursor.DEFAULT_CURSOR ) );
editorPane.repaint();
}
}
};
UIUtils.invokeLater( r );
}
protected final void maybeUnderlineIdentifierAt(Point mouseLocation) {
final ASTNode node = getASTNodeForLocation( mouseLocation );
if ( !(node instanceof SymbolReferenceNode) )
{
return;
}
final SymbolReferenceNode ref = (SymbolReferenceNode) node;
underlineLocation( ref.getTextRegion() );
}
protected final void underlineLocation(final ITextRegion region)
{
if ( region == null ) {
throw new IllegalArgumentException("region must not be NULL.");
}
Runnable r = new Runnable() {
@Override
public void run()
{
try
{
if ( currentUnderlineHighlight == null ) {
currentUnderlineHighlight = editorPane.getHighlighter().addHighlight(
region.getStartingOffset() ,
region.getEndOffset() ,
new UnderlineHighlightPainter(Color.BLUE,1) );
} else {
editorPane.getHighlighter().changeHighlight(
currentUnderlineHighlight,
region.getStartingOffset() ,
region.getEndOffset() );
}
editorPane.setCursor( Cursor.getPredefinedCursor( Cursor.HAND_CURSOR ) );
editorPane.repaint();
}
catch (BadLocationException e) {
LOG.error("underlineLocation(): Bad location "+region,e);
}
}
};
UIUtils.invokeLater( r );
}
protected final void clearHighlight() {
if ( currentHighlight != null ) {
editorPane.getHighlighter().removeHighlight( currentHighlight );
currentHighlight = null;
}
}
protected static enum Direction {
FORWARD {
@Override
public int advance(int index) {
return index+1;
}
} ,
BACKWARD {
@Override
public int advance(int index) {
return index-1;
}
} ;
public abstract int advance(int index);
}
protected final class SearchDialog extends JFrame {
private final JTextField searchPattern = new JTextField();
private final JCheckBox wrapSearch = new JCheckBox("Wrap",false);
private final JCheckBox caseSensitive = new JCheckBox("Match case",false);
private final JButton nextButton = new JButton("Next");
private final JButton previousButton = new JButton("Previous");
private final JButton closeButton = new JButton("Close");
private final JTextField messageArea = new JTextField("",25);
private String lastSearchPattern;
private int lastMatch = -1;
private int currentIndex = 0;
private Direction lastDirection = Direction.FORWARD;
public SearchDialog()
{
super("Search");
messageArea.setBackground(null);
messageArea.setEditable(false);
messageArea.setBorder(null);
messageArea.setFocusable(false);
setDefaultCloseOperation( JFrame.DO_NOTHING_ON_CLOSE );
final JPanel panel = new JPanel();
panel.setLayout( new GridBagLayout() );
// add search pattern
GridBagConstraints cnstrs = constraints( 0 , 0, true ,
false , GridBagConstraints.HORIZONTAL );
panel.add( searchPattern , cnstrs );
searchPattern.addActionListener( new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
search(lastDirection);
}
});
// add wrap checkbox
cnstrs = constraints( 0 , 1, false, false , GridBagConstraints.HORIZONTAL );
panel.add( wrapSearch , cnstrs );
// 'case-sensitive' checkbox
cnstrs = constraints( 1 , 1, false, false , GridBagConstraints.HORIZONTAL );
panel.add( caseSensitive , cnstrs );
// add message area
cnstrs = constraints( 0 , 2 , true , false , GridBagConstraints.HORIZONTAL );
panel.add( messageArea , cnstrs );
// create button panel
final JPanel buttonPanel = new JPanel();
buttonPanel.setLayout( new GridBagLayout() );
cnstrs = constraints( 0 , 0, false , true , GridBagConstraints.HORIZONTAL );
buttonPanel.add( previousButton , cnstrs );
previousButton.addActionListener( new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
search(Direction.BACKWARD);
}
});
cnstrs = constraints( 1 , 0, false , true , GridBagConstraints.HORIZONTAL );
buttonPanel.add( nextButton , cnstrs );
nextButton.addActionListener( new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
search(Direction.FORWARD);
}
});
cnstrs = constraints( 2 , 0, true , true , GridBagConstraints.HORIZONTAL );
buttonPanel.add( closeButton , cnstrs );
closeButton.addActionListener( new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
setVisible(false);
clearHighlight();
}
});
// add button panel
// add wrap checkbox
cnstrs = constraints( 0 , 3 , true , true , GridBagConstraints.HORIZONTAL );
panel.add( buttonPanel , cnstrs );
// add everything to content pane
getContentPane().add( panel );
setAlwaysOnTop(true);
pack();
}
public final void activate(String selectedText,int cursorPos)
{
setVisible(true);
currentIndex = cursorPos;
final String text = StringUtils.isNotBlank( selectedText ) ? selectedText : lastSearchPattern;
if ( text != null ) {
searchPattern.setText( selectedText );
}
searchPattern.requestFocus();
}
protected void search(Direction direction) {
showMessage(null);
final String source = getTextFromTextPane();
final String pattern = searchPattern.getText();
if ( StringUtils.isBlank( pattern ) ) {
showMessage("Please enter a search pattern");
return;
}
lastSearchPattern = pattern;
lastDirection = direction;
if ( lastMatch != -1 ) // advance past last match
{
if ( ! advance( source , direction , pattern , lastMatch ) ) {
clearHighlight();
showNotFoundMessage();
return;
}
}
// remember start index so we don't loop forever
// if the search pattern doesn't match at all
final int searchStartIndex = currentIndex;
final boolean matchCaseSensitive = caseSensitive.isSelected();
do {
final String currentText =
source.substring( currentIndex , currentIndex+pattern.length() );
final boolean matches;
if ( matchCaseSensitive ) {
matches = currentText.equals( pattern );
} else {
matches = currentText.equalsIgnoreCase( pattern );
}
if ( matches )
{
lastMatch = currentIndex;
gotoLocation( currentIndex );
editorPane.requestFocus();
editorPane.setCaretPosition( currentIndex );
highlightLocation( new TextRegion( currentIndex , pattern.length() ) );
// TODO: Maybe show 'match found' message ?
return;
}
if ( ! advance( source , direction , pattern , currentIndex ) ) {
clearHighlight();
showNotFoundMessage();
break;
}
} while ( currentIndex != searchStartIndex );
if ( lastMatch != -1 ) {
currentIndex = lastMatch;
}
clearHighlight();
showNotFoundMessage();
}
private void showNotFoundMessage() {
showMessage("No (more) matches");
}
private boolean advance(String source, Direction direction,String pattern,int index)
{
int newIndex = direction.advance( index );
if ( newIndex < 0 ) {
if ( wrapSearch.isSelected() )
{
showSearchWrappedMessage();
newIndex = source.length() - 1 - pattern.length() ;
} else {
return false;
}
} else if ( ( newIndex+pattern.length()) >= source.length() ) {
if ( wrapSearch.isSelected() ) {
showSearchWrappedMessage();
newIndex = 0;
} else {
return false;
}
}
currentIndex = newIndex;
return true;
}
private void showSearchWrappedMessage() {
showMessage("Search wrapped");
}
private void showMessage(String message) {
messageArea.setText( message );
}
}
protected final int getCaretPosition() {
return editorPane.getCaretPosition();
}
}