/*******************************************************************************
* Copyright (c) 2009 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
* Zend Technologies
*******************************************************************************/
package org.eclipse.php.internal.ui.compare;
import java.util.*;
import org.eclipse.compare.ICompareContainer;
import org.eclipse.compare.internal.MergeViewerAction;
import org.eclipse.compare.internal.Utilities;
import org.eclipse.core.commands.operations.IOperationHistory;
import org.eclipse.core.commands.operations.IOperationHistoryListener;
import org.eclipse.core.commands.operations.IUndoContext;
import org.eclipse.core.commands.operations.OperationHistoryEvent;
import org.eclipse.jface.action.*;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.preference.PreferenceConverter;
import org.eclipse.jface.text.*;
import org.eclipse.jface.text.source.*;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.editors.text.EditorsUI;
import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants;
import org.eclipse.ui.texteditor.FindReplaceAction;
import org.eclipse.wst.sse.ui.internal.StructuredTextViewer;
/**
* Extends the JFace SourceViewer with some convenience methods.
*/
public class MergeSourceViewer extends StructuredTextViewer implements
ISelectionChangedListener, ITextListener, IMenuListener,
IOperationHistoryListener {
public static final String UNDO_ID = "undo"; //$NON-NLS-1$
public static final String REDO_ID = "redo"; //$NON-NLS-1$
public static final String CUT_ID = "cut"; //$NON-NLS-1$
public static final String COPY_ID = "copy"; //$NON-NLS-1$
public static final String PASTE_ID = "paste"; //$NON-NLS-1$
public static final String DELETE_ID = "delete"; //$NON-NLS-1$
public static final String SELECT_ALL_ID = "selectAll"; //$NON-NLS-1$
public static final String SAVE_ID = "save"; //$NON-NLS-1$
public static final String FIND_ID = "find"; //$NON-NLS-1$
class TextOperationAction extends MergeViewerAction {
private int fOperationCode;
TextOperationAction(int operationCode, boolean mutable,
boolean selection, boolean content) {
this(operationCode, null, mutable, selection, content);
}
public TextOperationAction(int operationCode,
String actionDefinitionId, boolean mutable, boolean selection,
boolean content) {
super(mutable, selection, content);
if (actionDefinitionId != null)
setActionDefinitionId(actionDefinitionId);
fOperationCode = operationCode;
update();
}
public void run() {
if (isEnabled())
doOperation(fOperationCode);
}
public boolean isEnabled() {
return fOperationCode != -1 && canDoOperation(fOperationCode);
}
public void update() {
this.setEnabled(isEnabled());
}
}
private ResourceBundle fResourceBundle;
private Position fRegion;
private boolean fEnabled = true;
private HashMap fActions = new HashMap();
private IDocument fRememberedDocument;
private boolean fAddSaveAction = true;
private boolean isConfigured = false;
// line number ruler support
private IPropertyChangeListener fPreferenceChangeListener;
private boolean fShowLineNumber = false;
private LineNumberRulerColumn fLineNumberColumn;
private List textActions = new ArrayList();
public MergeSourceViewer(Composite parent, int style,
ResourceBundle bundle, ICompareContainer container) {
super(parent, new CompositeRuler(), null, false, style | SWT.H_SCROLL
| SWT.V_SCROLL);
fResourceBundle = bundle;
MenuManager menu = new MenuManager();
menu.setRemoveAllWhenShown(true);
menu.addMenuListener(this);
StyledText te = getTextWidget();
te.setMenu(menu.createContextMenu(te));
container.registerContextMenu(menu, this);
// for listening to editor show/hide line number preference value
fPreferenceChangeListener = new IPropertyChangeListener() {
public void propertyChange(PropertyChangeEvent event) {
MergeSourceViewer.this.handlePropertyChangeEvent(event);
}
};
EditorsUI.getPreferenceStore().addPropertyChangeListener(
fPreferenceChangeListener);
fShowLineNumber = EditorsUI
.getPreferenceStore()
.getBoolean(
AbstractDecoratedTextEditorPreferenceConstants.EDITOR_LINE_NUMBER_RULER);
if (fShowLineNumber) {
updateLineNumberRuler();
}
IOperationHistory history = getHistory();
if (history != null)
history.addOperationHistoryListener(this);
}
public void rememberDocument(IDocument doc) {
// if (doc != null && fRememberedDocument != null) {
// System.err.println("MergeSourceViewer.rememberDocument: fRememberedDocument != null: shouldn't happen"); //$NON-NLS-1$
// }
fRememberedDocument = doc;
}
public IDocument getRememberedDocument() {
return fRememberedDocument;
}
public void hideSaveAction() {
fAddSaveAction = false;
}
public void setFont(Font font) {
StyledText te = getTextWidget();
if (te != null)
te.setFont(font);
if (fLineNumberColumn != null) {
fLineNumberColumn.setFont(font);
layoutViewer();
}
}
public void setBackgroundColor(Color color) {
StyledText te = getTextWidget();
if (te != null)
te.setBackground(color);
if (fLineNumberColumn != null)
fLineNumberColumn.setBackground(color);
}
public void setEnabled(boolean enabled) {
if (enabled != fEnabled) {
fEnabled = enabled;
StyledText c = getTextWidget();
if (c != null) {
c.setEnabled(enabled);
Display d = c.getDisplay();
c.setBackground(enabled ? d
.getSystemColor(SWT.COLOR_LIST_BACKGROUND) : null);
}
}
}
public boolean getEnabled() {
return fEnabled;
}
public void setRegion(Position region) {
fRegion = region;
}
public Position getRegion() {
return fRegion;
}
public boolean isControlOkToUse() {
StyledText t = getTextWidget();
return t != null && !t.isDisposed();
}
public void setSelection(Position position) {
if (position != null)
setSelectedRange(position.getOffset(), position.getLength());
}
public void setLineBackground(Position position, Color c) {
StyledText t = getTextWidget();
if (t != null && !t.isDisposed()) {
Point region = new Point(0, 0);
getLineRange(position, region);
region.x -= getDocumentRegionOffset();
try {
t.setLineBackground(region.x, region.y, c);
} catch (IllegalArgumentException ex) {
// silently ignored
}
}
}
public void resetLineBackground() {
StyledText t = getTextWidget();
if (t != null && !t.isDisposed()) {
int lines = getLineCount();
t.setLineBackground(0, lines, null);
}
}
/*
* Returns number of lines in document region.
*/
public int getLineCount() {
IRegion region = getVisibleRegion();
int length = region.getLength();
if (length == 0)
return 0;
IDocument doc = getDocument();
int startLine = 0;
int endLine = 0;
int start = region.getOffset();
try {
startLine = doc.getLineOfOffset(start);
} catch (BadLocationException ex) {
// silently ignored
}
try {
endLine = doc.getLineOfOffset(start + length);
} catch (BadLocationException ex) {
// silently ignored
}
return endLine - startLine + 1;
}
public int getViewportLines() {
StyledText te = getTextWidget();
Rectangle clArea = te.getClientArea();
if (!clArea.isEmpty())
return clArea.height / te.getLineHeight();
return 0;
}
public int getViewportHeight() {
StyledText te = getTextWidget();
Rectangle clArea = te.getClientArea();
if (!clArea.isEmpty())
return clArea.height;
return 0;
}
/*
* Returns lines
*/
public int getDocumentRegionOffset() {
int start = getVisibleRegion().getOffset();
IDocument doc = getDocument();
if (doc != null) {
try {
return doc.getLineOfOffset(start);
} catch (BadLocationException ex) {
// silently ignored
}
}
return 0;
}
public int getVerticalScrollOffset() {
StyledText st = getTextWidget();
int lineHeight = st.getLineHeight();
return getTopInset()
- ((getDocumentRegionOffset() * lineHeight) + st.getTopPixel());
}
/*
* Returns the start line and the number of lines which correspond to the
* given position. Starting line number is 0 based.
*/
public Point getLineRange(Position p, Point region) {
IDocument doc = getDocument();
if (p == null || doc == null) {
region.x = 0;
region.y = 0;
return region;
}
int start = p.getOffset();
int length = p.getLength();
int startLine = 0;
try {
startLine = doc.getLineOfOffset(start);
} catch (BadLocationException e) {
// silently ignored
}
int lineCount = 0;
if (length == 0) {
// // if range length is 0 and if range starts a new line
// try {
// if (start == doc.getLineStartOffset(startLine)) {
// lines--;
// }
// } catch (BadLocationException e) {
// lines--;
// }
} else {
int endLine = 0;
try {
endLine = doc.getLineOfOffset(start + length - 1); // why -1?
} catch (BadLocationException e) {
// silently ignored
}
lineCount = endLine - startLine + 1;
}
region.x = startLine;
region.y = lineCount;
return region;
}
/*
* Scroll TextPart to the given line.
*/
public void vscroll(int line) {
int srcViewSize = getLineCount();
int srcExtentSize = getViewportLines();
if (srcViewSize > srcExtentSize) {
if (line < 0)
line = 0;
int cp = getTopIndex();
if (cp != line)
setTopIndex(line + getDocumentRegionOffset());
}
}
public void addAction(String actionId, MergeViewerAction action) {
fActions.put(actionId, action);
}
public IAction getAction(String actionId) {
IAction action = (IAction) fActions.get(actionId);
if (action == null) {
action = createAction(actionId);
if (action == null)
return null;
if (action instanceof MergeViewerAction) {
MergeViewerAction mva = (MergeViewerAction) action;
if (mva.isContentDependent())
addTextListener(this);
if (mva.isSelectionDependent())
addSelectionChangedListener(this);
Utilities.initAction(action, fResourceBundle,
"action." + actionId + "."); //$NON-NLS-1$ //$NON-NLS-2$
}
addAction(actionId, action);
}
if (action instanceof MergeViewerAction) {
MergeViewerAction mva = (MergeViewerAction) action;
if (mva.isEditableDependent() && !isEditable())
return null;
}
return action;
}
protected IAction createAction(String actionId) {
if (UNDO_ID.equals(actionId))
return new TextOperationAction(UNDO,
"org.eclipse.ui.edit.undo", true, false, true); //$NON-NLS-1$
if (REDO_ID.equals(actionId))
return new TextOperationAction(REDO,
"org.eclipse.ui.edit.redo", true, false, true); //$NON-NLS-1$
if (CUT_ID.equals(actionId))
return new TextOperationAction(CUT,
"org.eclipse.ui.edit.cut", true, true, false); //$NON-NLS-1$
if (COPY_ID.equals(actionId))
return new TextOperationAction(COPY,
"org.eclipse.ui.edit.copy", false, true, false); //$NON-NLS-1$
if (PASTE_ID.equals(actionId))
return new TextOperationAction(PASTE,
"org.eclipse.ui.edit.paste", true, false, false); //$NON-NLS-1$
if (DELETE_ID.equals(actionId))
return new TextOperationAction(DELETE,
"org.eclipse.ui.edit.delete", true, false, false); //$NON-NLS-1$
if (SELECT_ALL_ID.equals(actionId))
return new TextOperationAction(SELECT_ALL,
"org.eclipse.ui.edit.selectAll", false, false, false); //$NON-NLS-1$
return null;
}
public void selectionChanged(SelectionChangedEvent event) {
Iterator e = fActions.values().iterator();
while (e.hasNext()) {
Object next = e.next();
if (next instanceof MergeViewerAction) {
MergeViewerAction action = (MergeViewerAction) next;
if (action.isSelectionDependent())
action.update();
}
}
}
public void textChanged(TextEvent event) {
updateContentDependantActions();
}
void updateContentDependantActions() {
Iterator e = fActions.values().iterator();
while (e.hasNext()) {
Object next = e.next();
if (next instanceof MergeViewerAction) {
MergeViewerAction action = (MergeViewerAction) next;
if (action.isContentDependent())
action.update();
}
}
}
/*
* Allows the viewer to add menus and/or tools to the context menu.
*/
public void menuAboutToShow(IMenuManager menu) {
menu.add(new Separator("undo")); //$NON-NLS-1$
addMenu(menu, UNDO_ID);
addMenu(menu, REDO_ID);
menu.add(new GroupMarker("save")); //$NON-NLS-1$
if (fAddSaveAction)
addMenu(menu, SAVE_ID);
menu.add(new Separator("file")); //$NON-NLS-1$
menu.add(new Separator("ccp")); //$NON-NLS-1$
addMenu(menu, CUT_ID);
addMenu(menu, COPY_ID);
addMenu(menu, PASTE_ID);
addMenu(menu, DELETE_ID);
addMenu(menu, SELECT_ALL_ID);
menu.add(new Separator("edit")); //$NON-NLS-1$
menu.add(new Separator("find")); //$NON-NLS-1$
addMenu(menu, FIND_ID);
menu.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
menu.add(new Separator("text")); //$NON-NLS-1$
for (Iterator iterator = textActions.iterator(); iterator.hasNext();) {
IAction action = (IAction) iterator.next();
menu.add(action);
}
menu.add(new Separator("rest")); //$NON-NLS-1$
// update all actions
// to get undo redo right
updateActions();
}
private void addMenu(IMenuManager menu, String actionId) {
IAction action = getAction(actionId);
if (action != null)
menu.add(action);
}
protected void handleDispose() {
removeTextListener(this);
removeSelectionChangedListener(this);
EditorsUI.getPreferenceStore().removePropertyChangeListener(
fPreferenceChangeListener);
IOperationHistory history = getHistory();
if (history != null)
history.removeOperationHistoryListener(this);
super.handleDispose();
}
/**
* update all actions independent of their type
*
*/
public void updateActions() {
Iterator e = fActions.values().iterator();
while (e.hasNext()) {
Object next = e.next();
if (next instanceof MergeViewerAction) {
MergeViewerAction action = (MergeViewerAction) next;
action.update();
}
if (next instanceof FindReplaceAction) {
FindReplaceAction action = (FindReplaceAction) next;
action.update();
}
}
}
public void configure(SourceViewerConfiguration configuration) {
if (isConfigured)
unconfigure();
isConfigured = true;
super.configure(configuration);
}
/**
* specific implementation to support a vertical ruler
*
* @param x
* @param y
* @param width
* @param height
*/
public void setBounds(int x, int y, int width, int height) {
if (getControl() instanceof Composite) {
((Composite) getControl()).setBounds(x, y, width, height);
} else {
getTextWidget().setBounds(x, y, width, height);
}
}
/**
* handle show/hide line numbers from editor preferences
*
* @param event
*/
protected void handlePropertyChangeEvent(PropertyChangeEvent event) {
String key = event.getProperty();
if (key
.equals(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_LINE_NUMBER_RULER)) {
boolean b = EditorsUI
.getPreferenceStore()
.getBoolean(
AbstractDecoratedTextEditorPreferenceConstants.EDITOR_LINE_NUMBER_RULER);
if (b != fShowLineNumber) {
toggleLineNumberRuler();
}
} else if (key
.equals(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_LINE_NUMBER_RULER_COLOR)) {
updateLineNumberColumnPresentation(true);
}
}
/**
* Hides or shows line number ruler column based of preference setting
*/
private void updateLineNumberRuler() {
IVerticalRuler v = getVerticalRuler();
if (v != null && v instanceof CompositeRuler) {
CompositeRuler c = (CompositeRuler) v;
if (!fShowLineNumber) {
if (fLineNumberColumn != null) {
c.removeDecorator(fLineNumberColumn);
}
} else {
if (fLineNumberColumn == null) {
fLineNumberColumn = new LineNumberRulerColumn();
updateLineNumberColumnPresentation(false);
}
c.addDecorator(0, fLineNumberColumn);
}
}
}
private void updateLineNumberColumnPresentation(boolean refresh) {
if (fLineNumberColumn == null)
return;
RGB rgb = getColorFromStore(
EditorsUI.getPreferenceStore(),
AbstractDecoratedTextEditorPreferenceConstants.EDITOR_LINE_NUMBER_RULER_COLOR);
if (rgb == null)
rgb = new RGB(0, 0, 0);
ISharedTextColors sharedColors = getSharedColors();
fLineNumberColumn.setForeground(sharedColors.getColor(rgb));
if (refresh) {
fLineNumberColumn.redraw();
}
}
private void layoutViewer() {
Control parent = getControl();
if (parent instanceof Composite && !parent.isDisposed())
((Composite) parent).layout(true);
}
private ISharedTextColors getSharedColors() {
return EditorsUI.getSharedTextColors();
}
private RGB getColorFromStore(IPreferenceStore store, String key) {
RGB rgb = null;
if (store.contains(key)) {
if (store.isDefault(key))
rgb = PreferenceConverter.getDefaultColor(store, key);
else
rgb = PreferenceConverter.getColor(store, key);
}
return rgb;
}
/**
* Toggles line number ruler column.
*/
private void toggleLineNumberRuler() {
fShowLineNumber = !fShowLineNumber;
updateLineNumberRuler();
}
public void addTextAction(IAction textEditorPropertyAction) {
textActions.add(textEditorPropertyAction);
}
public void addAction(String id, IAction action) {
fActions.put(id, action);
}
private IOperationHistory getHistory() {
if (PlatformUI.getWorkbench() == null) {
return null;
}
return PlatformUI.getWorkbench().getOperationSupport()
.getOperationHistory();
}
public void historyNotification(OperationHistoryEvent event) {
// This method updates the enablement of all content operations
// when the undo history changes. It could be localized to UNDO and
// REDO.
IUndoContext context = getUndoContext();
if (context != null && event.getOperation().hasContext(context)) {
Display.getDefault().asyncExec(new Runnable() {
public void run() {
updateContentDependantActions();
}
});
}
}
private IUndoContext getUndoContext() {
IUndoManager undoManager = getUndoManager();
if (undoManager instanceof IUndoManagerExtension)
return ((IUndoManagerExtension) undoManager).getUndoContext();
return null;
}
}