/******************************************************************************* * 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); } } } }