/*******************************************************************************
* Copyright (c) 2007 Exadel, Inc. and Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is 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:
* Exadel, Inc. and Red Hat, Inc. - initial API and implementation
******************************************************************************/
package org.jboss.tools.common.model.ui.texteditors.xmleditor;
import java.util.Properties;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.TextEvent;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.source.IVerticalRuler;
import org.eclipse.jface.text.source.SourceViewerConfiguration;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DragSource;
import org.eclipse.swt.dnd.DropTarget;
import org.eclipse.swt.dnd.DropTargetAdapter;
import org.eclipse.swt.dnd.DropTargetEvent;
import org.eclipse.swt.dnd.DropTargetListener;
import org.eclipse.swt.dnd.FileTransfer;
import org.eclipse.swt.dnd.HTMLTransfer;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.editors.text.ILocationProvider;
import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion;
import org.eclipse.wst.sse.ui.StructuredTextEditor;
import org.eclipse.wst.sse.ui.StructuredTextViewerConfiguration;
import org.eclipse.wst.sse.ui.internal.StructuredTextViewer;
import org.eclipse.wst.xml.core.internal.document.AttrImpl;
import org.eclipse.wst.xml.core.internal.document.ElementImpl;
import org.eclipse.wst.xml.ui.StructuredTextViewerConfigurationXML;
import org.eclipse.wst.xml.ui.internal.XMLUIPlugin;
import org.jboss.tools.common.meta.action.XActionInvoker;
import org.jboss.tools.common.model.XModelBuffer;
import org.jboss.tools.common.model.XModelException;
import org.jboss.tools.common.model.XModelObject;
import org.jboss.tools.common.model.XModelTransferBuffer;
import org.jboss.tools.common.model.filesystems.impl.FileAnyImpl;
import org.jboss.tools.common.model.ui.ModelUIPlugin;
import org.jboss.tools.common.model.ui.dnd.ModelTransfer;
import org.jboss.tools.common.model.ui.editor.EditorDescriptor;
import org.jboss.tools.common.model.ui.editor.IModelObjectEditorInput;
import org.jboss.tools.common.model.ui.editors.dnd.DropCommandFactory;
import org.jboss.tools.common.model.ui.editors.dnd.DropData;
import org.jboss.tools.common.model.ui.editors.dnd.EmptyTagProposalFactory;
import org.jboss.tools.common.model.ui.editors.dnd.context.DropContext;
import org.jboss.tools.common.model.ui.editors.dnd.context.IDNDTextEditor;
import org.jboss.tools.common.model.ui.texteditors.dnd.TextEditorDrop;
import org.jboss.tools.common.model.ui.texteditors.dnd.TextEditorDropProvider;
import org.jboss.tools.common.model.ui.views.palette.XModelPaletteInsertHelper;
import org.jboss.tools.common.model.util.XModelObjectLoaderUtil;
import org.jboss.tools.common.text.xml.IOccurrencePreferenceProvider;
import org.jboss.tools.common.text.xml.XMLTextViewerConfiguration;
import org.jboss.tools.common.text.xml.XmlEditorPlugin;
import org.jboss.tools.common.text.xml.ui.FreeCaretStyledText;
import org.jboss.tools.jst.jsp.text.xpl.IStructuredTextOccurrenceStructureProvider;
import org.w3c.dom.DocumentType;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.Text;
/**
* @author Jeremy
*
*/
public class XMLTextEditor extends StructuredTextEditor implements IDocumentListener, IDNDTextEditor, IOccurrencePreferenceProvider {
private IStructuredTextOccurrenceStructureProvider fOccurrenceModelUpdater;
/// TextEditorDrop dnd = new TextEditorDrop();
IEditorInput input;
XModelObject object = null;
public XMLTextEditor() {
this (true);
}
public XMLTextEditor(final boolean useRHDSConfig) {
this.useRHDSConfig = useRHDSConfig;
//XmlEditorPlugin.getDefault().initDefaultPluginPreferences();
/// dnd.setTextEditorDropProvider(new TextEditorDropProviderImpl());
if (useRHDSConfig) {
super.setSourceViewerConfiguration(new XMLTextViewerConfiguration());
}
}
private boolean useRHDSConfig = true;
protected void setSourceViewerConfiguration(SourceViewerConfiguration config) {
if( useRHDSConfig
&& !(config instanceof XMLTextViewerConfiguration)
&& (config instanceof StructuredTextViewerConfigurationXML
|| !(config instanceof StructuredTextViewerConfiguration)
)
) {
XMLTextViewerConfiguration r = new XMLTextViewerConfiguration();
r.setInitialConfiguration(config);
config = r;
}
super.setSourceViewerConfiguration(config);
}
protected void initializeDrop(ITextViewer textViewer) {
//fake drop to make fDropTarget != null.
Composite c = textViewer.getTextWidget();
Label l = new Label(c, SWT.NONE);
// TODO migration to new WTP is needed
// fDropTarget = new DropTarget(l, 0);
l.dispose();
}
public IStructuredTextOccurrenceStructureProvider getOccurrencePreferenceProvider() {
return fOccurrenceModelUpdater;
}
public String getEditorId() {
return XMLUIPlugin.ID;
}
public void createPartControl(Composite parent) {
super.createPartControl(parent);
fOccurrenceModelUpdater= XmlEditorPlugin.getDefault().getOccurrenceStructureProviderRegistry(XmlEditorPlugin.PLUGIN_ID).getCurrentOccurrenceProvider(XmlEditorPlugin.PLUGIN_ID);
if (fOccurrenceModelUpdater != null)
fOccurrenceModelUpdater.install(this, getTextViewer());
/// dnd.enable();
createDrop();
setModified(false);
getDocumentListenerRegister().unregister();
getDocumentListenerRegister().register();
Object dtid =
getSourceViewer().getTextWidget().getData("DropTarget"); //$NON-NLS-1$
if (dtid != null) {
if (dtid instanceof DropTarget) {
DropTarget dropTarget = (DropTarget) dtid;
dropTarget.addDropListener(new DropTargetAdapter() {
private FreeCaretStyledText getFreeCaretControl(Object sourceOrTarget) {
if (sourceOrTarget == null)
return null;
Object control = null;
if (sourceOrTarget instanceof DropTarget) {
control =
((DropTarget) sourceOrTarget).getControl();
} else if (sourceOrTarget instanceof DragSource) {
control =
((DragSource) sourceOrTarget).getControl();
} else
return null;
if (control instanceof FreeCaretStyledText)
return (FreeCaretStyledText) control;
return null;
}
public void dragEnter(DropTargetEvent event) {
FreeCaretStyledText fcst =getFreeCaretControl(
event.widget);
if(fcst != null) {
fcst.enableFreeCaret(true);
}
}
public void dragLeave(DropTargetEvent event) {
FreeCaretStyledText fcst =getFreeCaretControl(
event.widget);
if(fcst != null) {
fcst.enableFreeCaret(false);
}
}
public void dragOperationChanged(DropTargetEvent event) {
FreeCaretStyledText fcst =getFreeCaretControl(
event.widget);
if(fcst != null) {
fcst.enableFreeCaret(false);
}
}
public void dragOver(DropTargetEvent event) {
FreeCaretStyledText fcst =
getFreeCaretControl(event.widget);
if(fcst != null) {
int pos = getPosition(fcst, event.x, event.y);
Point p = fcst.getLocationAtOffset(pos);
fcst.myRedraw(p.x, p.y);
}
}
public void drop(DropTargetEvent event) {
FreeCaretStyledText fcst =getFreeCaretControl(
event.widget);
if(fcst != null) {
fcst.enableFreeCaret(false);
}
}
});
}
}
}
/*
* @see org.eclipse.ui.texteditor.AbstractTextEditor#createSourceViewer(Composite, IVerticalRuler, int)
* @since 2.1
*/
protected ISourceViewer createSourceViewer(Composite parent, IVerticalRuler ruler, int styles) {
ISourceViewer sv = super.createSourceViewer(parent, ruler, styles);
sv.getTextWidget().addFocusListener(new TextFocusListener());
// sv.addTextListener(this);
return sv;
}
protected StructuredTextViewer createStructedTextViewer(Composite parent, IVerticalRuler verticalRuler, int styles) {
return new StructuredTextViewer(parent, verticalRuler, getOverviewRuler(), isOverviewRulerVisible(), styles) {
/**
* Factory method to create the text widget to be used as the viewer's text widget.
*
* @return the text widget to be used
*/
protected StyledText createTextWidget(Composite parent, int styles) {
return new FreeCaretStyledText(parent, styles);
}
};
}
class TextFocusListener extends FocusAdapter {
public void focusLost(FocusEvent e) {
saveInThread();
}
}
private void saveInThread() {
if(!XMLTextEditor.super.isDirty()) return;
Display.getDefault().syncExec(
new Runnable() {
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
//ignore
}
save();
}
}
);
}
public void save() {
if(!lock && isModified()) {
lock = true;
try {
FileAnyImpl f = (FileAnyImpl)getModelObject();
if(f != null) f.edit(getSourceViewer().getDocument().get());
} catch (XModelException e) {
ModelUIPlugin.getPluginLog().logError(e);
} finally {
setModified(false);
lock = false;
}
}
getSourceViewer().getAnnotationModel().disconnect(getSourceViewer().getDocument());
getSourceViewer().getAnnotationModel().connect(getSourceViewer().getDocument());
}
boolean modified = false;
public void setModified(boolean set) {
if (this.modified != set) {
this.modified = set;
if(set) {
XModelObject o = getModelObject();
if(o != null) o.setModified(true);
}
super.firePropertyChange(IEditorPart.PROP_DIRTY);
}
}
public boolean isModified() {
return this.modified;
}
protected void doSetInput(IEditorInput input) throws CoreException {
super.doSetInput(input);
this.input = input;
if (input instanceof IModelObjectEditorInput){
object = ((IModelObjectEditorInput)input).getXModelObject();
}
if(getSourceViewer() != null && getSourceViewer().getDocument() != null) {
getDocumentListenerRegister().unregister();
getDocumentListenerRegister().register();
}
}
/*
* @see org.eclipse.ui.editors.text.TextEditor#getAdapter(Class)
*/
public Object getAdapter( Class adapter ) {
if (adapter == EditorDescriptor.class)
return new EditorDescriptor("xml"); //$NON-NLS-1$
return super.getAdapter( adapter );
}
boolean lock = false;
public boolean isDirty() {
if (getEditorInput() instanceof IModelObjectEditorInput) {
XModelObject o = getModelObject();
if(o != null && o.isModified())
return true;
else {
return isModified();
}
} else {
return super.isDirty();
}
}
public boolean isSaveOnCloseNeeded() {
return super.isSaveOnCloseNeeded() || isModified();
}
public void doSave(IProgressMonitor monitor) {
// ModelUIPlugin.log(getSourceViewer().getDocument().get());
XModelObject o = getModelObject();
if(o != null) {
save();
if(!(getEditorInput() instanceof IFileEditorInput) &&
(getEditorInput() instanceof ILocationProvider)) {
doSaveYourself();
} else {
o.setModified(false);
XModelObjectLoaderUtil.updateModifiedOnSave(o);
}
}
superDoSave(monitor);
}
protected final void superDoSave(IProgressMonitor monitor) {
super.doSave(monitor);
}
protected void doSaveYourself() {}
public XModelObject getModelObject() {
if (getEditorInput() instanceof IModelObjectEditorInput) {
return ((IModelObjectEditorInput)getEditorInput()).getXModelObject();
}
return null;
}
class TextEditorDropProviderImpl implements TextEditorDropProvider, TextEditorDrop.TextEditorDropProvider2 {
public ISourceViewer getSourceViewer() {
return XMLTextEditor.this.getSourceViewer();
}
public XModelObject getModelObject() {
return XMLTextEditor.this.getModelObject();
}
public void insert(Properties p) {
XModelPaletteInsertHelper.getInstance().insertIntoEditor(getSourceViewer(), p);
}
public String getContext(int pos) {
return _getContext(pos);
}
}
int lastpos = -1;
String lastContext = null;
private String _getContext(int pos) {
if(lastpos == pos && pos >= 0) {
pos = lastpos;
return lastContext;
}
lastpos = pos;
lastContext = null;
getSourceViewer().getDocument();
IndexedRegion region = getModel().getIndexedRegion(pos);
if(region instanceof ElementImpl) {
ElementImpl jspElement = (ElementImpl)region;
NamedNodeMap attributes = jspElement.getAttributes();
if(pos==jspElement.getStartOffset()
|| pos==jspElement.getEndStartOffset()) {
return lastContext = "text"; //$NON-NLS-1$
}
for(int i = 0;i<attributes.getLength();i++ ) {
Node attribute = attributes.item(i);
if(attribute instanceof AttrImpl) {
AttrImpl jspAttr = (AttrImpl)attribute;
ITextRegion valueRegion = jspAttr.getValueRegion();
if(valueRegion == null) {
return lastContext = "none"; //$NON-NLS-1$
}
int startPos = jspElement.getStartOffset()+ valueRegion.getStart();
int endPos = jspElement.getStartOffset() + valueRegion.getTextEnd();
if(pos > startPos && pos < endPos) {
return lastContext = "attribute"; //$NON-NLS-1$
}
}
}
return lastContext = "none"; //$NON-NLS-1$
} else if(region instanceof Text) {
return lastContext = "text"; //$NON-NLS-1$
} else if(region instanceof DocumentType) {
return lastContext = "none"; //$NON-NLS-1$
} else if(region == null) {
//new place
return lastContext = "text"; //$NON-NLS-1$
}
return lastContext = "none"; //$NON-NLS-1$
}
public void textChanged(TextEvent event) {
setModified(super.isSaveOnCloseNeeded());
}
public void documentAboutToBeChanged(DocumentEvent event) {}
public void documentChanged(DocumentEvent event) {
textChanged(null);
StyledText text = getSourceViewer() != null ? getSourceViewer().getTextWidget() : null;
if(text != null && !text.isDisposed()) {
if(!text.isFocusControl() && text.isVisible()) {
saveInThread();
}
}
}
public void doRevertToSaved() {
save();
XModelObject o = getModelObject();
Properties p = new Properties();
XActionInvoker.invoke("FileTXT", "DiscardActions.Discard", o, p); //$NON-NLS-1$ //$NON-NLS-2$
if(!"true".equals(p.getProperty("done"))) return; //$NON-NLS-1$ //$NON-NLS-2$
super.doRevertToSaved();
if(o.isModified()) o.setModified(false);
modified = false;
firePropertyChange(IEditorPart.PROP_DIRTY);
updatePartControl(getEditorInput());
}
public void dispose() {
getDocumentListenerRegister().unregister();
documentListenerRegistry = null;
if (fOccurrenceModelUpdater != null) {
fOccurrenceModelUpdater.uninstall();
fOccurrenceModelUpdater = null;
}
super.dispose();
}
//////dnd
private void createDrop() {
DropTarget target = new DropTarget(getSourceViewer().getTextWidget(), DND.DROP_MOVE | DND.DROP_COPY);
Transfer[] types = new Transfer[] {ModelTransfer.getInstance(), HTMLTransfer.getInstance(), TextTransfer.getInstance(), FileTransfer.getInstance()};
target.setTransfer(types);
target.addDropListener(new DTL());
}
class DTL implements DropTargetListener {
DropContext dropContext = new DropContext();
int lastpos = -1;
int lastdetail = -1;
public void dragEnter(DropTargetEvent event) {
lastpos = -1;
}
public void dragLeave(DropTargetEvent event) {
lastpos = -1;
}
public void dragOperationChanged(DropTargetEvent event) {
}
public void dragOver(DropTargetEvent event) {
if(!isEditable() || (getModelObject() != null && !getModelObject().isObjectEditable())) {
event.detail = DND.DROP_NONE;
return;
}
dropContext.setDropTargetEvent(event);
if(dropContext.getFlavor() == null) {
event.detail = DND.DROP_NONE;
return;
}
// // Drop from VPE to Source is forbidden
// if(dropContext.getFlavor().equals("text/html")) { //$NON-NLS-1$
// if(InnerDragBuffer.getInnerDragObject()!= null) {
// event.detail = DND.DROP_NONE;
// }
// return;
// }
int pos = getPosition(event.x, event.y);
if(lastpos == pos && pos >= 0) {
pos = lastpos;
event.detail = lastdetail;
return;
}
lastpos = pos;
dropContext.clean();
getSourceViewer().getDocument();
IndexedRegion region = getModel().getIndexedRegion(pos);
if(region instanceof ElementImpl) {
ElementImpl jspElement = (ElementImpl)region;
NamedNodeMap attributes = jspElement.getAttributes();
if(pos==jspElement.getStartOffset()
|| pos==jspElement.getEndStartOffset()) {
event.detail = lastdetail = DND.DROP_MOVE;
return;
}
for(int i = 0;i<attributes.getLength();i++ ) {
Node attribute = attributes.item(i);
if(attribute instanceof AttrImpl) {
AttrImpl jspAttr = (AttrImpl)attribute;
ITextRegion valueRegion = jspAttr.getValueRegion();
if(valueRegion == null) {
event.detail = lastdetail = DND.DROP_NONE;
return;
}
int startPos = jspElement.getStartOffset()+ valueRegion.getStart();
int endPos = jspElement.getStartOffset() + valueRegion.getTextEnd();
if(pos>startPos && pos<endPos) {
dropContext.setOverAttributeValue(true);
event.detail = lastdetail = DND.DROP_MOVE;
return;
}
}
}
event.detail = lastdetail = DND.DROP_NONE;
} else if(region instanceof Text) {
event.detail = lastdetail = DND.DROP_MOVE;
} else if(region instanceof DocumentType) {
event.detail = lastdetail = DND.DROP_NONE;
} else if(region == null) {
//new place
event.detail = lastdetail = DND.DROP_MOVE;
}
}
public void drop(DropTargetEvent event) {
int offset = getPosition(event.x, event.y);
selectAndReveal(offset, 0);
dropContext.runDropCommand(XMLTextEditor.this, event);
}
public void dropAccept(DropTargetEvent event) {
}
}
public void runDropCommand(final String flavor, final String data) {
XModelBuffer b = XModelTransferBuffer.getInstance().getBuffer();
final XModelObject o = b == null ? null : b.source();
Display.getDefault().asyncExec(new Runnable() {
public void run() {
if(o != null && !XModelTransferBuffer.getInstance().isEnabled()) {
XModelTransferBuffer.getInstance().enable();
XModelTransferBuffer.getInstance().getBuffer().addSource(o);
}
try {
DropCommandFactory.getInstance()
.getDropCommand(flavor, EmptyTagProposalFactory.getInstance())
.execute(
new DropData(
flavor,
data,
getEditorInput(),
getSourceViewer(),
getSelectionProvider()
)
);
} finally {
XModelTransferBuffer.getInstance().disable();
}
}
});
}
private int getPosition(int x, int y) {
ISourceViewer v = getSourceViewer();
if(v == null) return 0;
return getPosition(v.getTextWidget(), x, y);
}
private int getPosition(StyledText t, int x, int y) {
if(t == null || t.isDisposed()) return 0;
Point pp = t.toControl(x, y);
x = pp.x;
y = pp.y;
int lineIndex = (t.getTopPixel() + y) / t.getLineHeight();
if (lineIndex >= t.getLineCount()) {
return t.getCharCount();
} else {
int c = 0;
try {
c = t.getOffsetAtLocation(new Point(x, y));
if (c < 0) c = 0;
} catch (IllegalArgumentException ex) {
//do not log, catching that exception is
//the way to know that we are out of line.
if (lineIndex + 1 >= t.getLineCount()) {
return t.getCharCount();
}
c = t.getOffsetAtLine(lineIndex + 1)
- (t.getLineDelimiter() == null
? 0 : t.getLineDelimiter().length());
}
return c;
}
}
private DocumentListenerRegistry documentListenerRegistry;
protected DocumentListenerRegistry getDocumentListenerRegister() {
if(documentListenerRegistry == null) {
documentListenerRegistry = new DocumentListenerRegistry();
}
return documentListenerRegistry;
}
protected class DocumentListenerRegistry {
IDocument document = null;
public void unregister() {
if(document != null) {
document.removeDocumentListener(XMLTextEditor.this);
}
}
public void register() {
if(getSourceViewer() == null) return;
document = getSourceViewer().getDocument();
if(document != null) {
document.addDocumentListener(XMLTextEditor.this);
}
}
}
}