/* ****************************************************************************** * Copyright (c) 2006-2012 XMind Ltd. and others. * * This file is a part of XMind 3. XMind releases 3 and * above are dual-licensed under the Eclipse Public License (EPL), * which is available at http://www.eclipse.org/legal/epl-v10.html * and the GNU Lesser General Public License (LGPL), * which is available at http://www.gnu.org/licenses/lgpl.html * See http://www.xmind.net/license.html for details. * * Contributors: * XMind Ltd. - initial API and implementation *******************************************************************************/ package org.xmind.ui.richtext; import org.eclipse.jface.action.IMenuListener; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.action.MenuManager; import org.eclipse.jface.action.ToolBarManager; import org.eclipse.jface.text.DefaultTextDoubleClickStrategy; import org.eclipse.jface.text.Document; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IDocumentExtension3; import org.eclipse.jface.text.ITextInputListener; import org.eclipse.jface.text.ITextListener; import org.eclipse.jface.text.TextViewer; import org.eclipse.jface.text.presentation.PresentationReconciler; import org.eclipse.jface.viewers.ISelection; 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.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.ToolBar; /** * @author Frank Shaka */ public class RichTextEditViewer implements IRichTextEditViewer { private static class LessLatencyTextViewer extends TextViewer { private LessLatencyTextViewer(Composite parent, int styles) { super(parent, styles); } @Override protected int getEmptySelectionChangedEventDelay() { return 100; } } private IRichDocument document; private Composite control; private TextViewer textViewer; private boolean editable; private IRichTextRenderer renderer; private IRichTextActionBarContributor contributor; private ToolBarManager toolBarManager = null; private MenuManager contextMenu = null; public RichTextEditViewer(Composite parent) { this(parent, DEFAULT_CONTROL_STYLE, null); } public RichTextEditViewer(Composite parent, IRichTextActionBarContributor contributor) { this(parent, DEFAULT_CONTROL_STYLE, contributor); } public RichTextEditViewer(Composite parent, int textControlStyle) { this(parent, textControlStyle, null); } public RichTextEditViewer(Composite parent, int textControlStyle, IRichTextActionBarContributor contributor) { this.contributor = contributor; this.editable = (textControlStyle & SWT.READ_ONLY) == 0; this.control = createControl(parent, textControlStyle); } protected Composite createControl(Composite parent, int textControlStyle) { Composite composite = new Composite(parent, SWT.NONE); composite.setBackground( parent.getDisplay().getSystemColor(SWT.COLOR_WHITE)); GridLayout layout = new GridLayout(); layout.marginHeight = 0; layout.marginWidth = 0; layout.horizontalSpacing = 0; layout.verticalSpacing = 0; composite.setLayout(layout); createContentArea(composite, textControlStyle); composite.addDisposeListener(new DisposeListener() { public void widgetDisposed(DisposeEvent e) { handleControlDispose(e); } }); return composite; } protected void createContentArea(Composite parent, int textControlStyle) { Control toolBar = createToolBar(parent); if (toolBar != null) { createSeparator(parent); } createTextControl(parent, textControlStyle); } protected Control createToolBar(Composite parent) { if (contributor == null) return null; contributor.init(this); toolBarManager = new ToolBarManager(SWT.FLAT | SWT.WRAP); ToolBar toolBar = toolBarManager.createControl(parent); contributor.fillToolBar(toolBarManager); parent.addListener(SWT.Resize, new Listener() { public void handleEvent(Event event) { toolBarManager.update(true); } }); toolBar.setBackground( parent.getDisplay().getSystemColor(SWT.COLOR_WHITE)); toolBar.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); return toolBar; } protected void createSeparator(Composite parent) { Label sep = new Label(parent, SWT.SEPARATOR | SWT.HORIZONTAL); sep.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); sep.setBackground(parent.getDisplay() .getSystemColor(SWT.COLOR_WIDGET_BACKGROUND)); } protected Control createTextControl(Composite parent, int style) { textViewer = createTextViewer(parent, style); initTextViewer(textViewer); renderer = createRenderer(textViewer); Control textControl = textViewer.getControl(); textControl.setBackground(parent.getBackground()); textControl.setForeground( parent.getDisplay().getSystemColor(SWT.COLOR_BLACK)); GridData gridData = new GridData(GridData.FILL_BOTH); gridData.heightHint = 160; gridData.horizontalIndent = 2; gridData.verticalIndent = 2; textControl.setLayoutData(gridData); return textControl; } protected TextViewer createTextViewer(Composite parent, int style) { return new LessLatencyTextViewer(parent, style); } protected RichTextRenderer createRenderer(TextViewer textViewer) { return new RichTextRenderer(textViewer); } private void initTextViewer(final TextViewer textViewer) { textViewer.addPostSelectionChangedListener( new ISelectionChangedListener() { public void selectionChanged(SelectionChangedEvent event) { updateToolBar(event.getSelection()); } }); Control control = textViewer.getTextWidget(); createContentPopupMenu(control); textViewer.setTextDoubleClickStrategy( new DefaultTextDoubleClickStrategy(), IDocument.DEFAULT_CONTENT_TYPE); //((StyledText) textViewer.getControl()).setLineSpacing(3); textViewer.setUndoManager(new RichTextViewerUndoManager(25)); textViewer.activatePlugins(); addHyperlinkListener(textViewer); } private void createContentPopupMenu(Control control) { contextMenu = new MenuManager(); contextMenu.setRemoveAllWhenShown(true); contextMenu.addMenuListener(new IMenuListener() { public void menuAboutToShow(IMenuManager manager) { fillContextMenu(manager); } }); control.setMenu(contextMenu.createContextMenu(control)); } private void fillContextMenu(IMenuManager menu) { if (contributor != null) contributor.fillContextMenu(menu); } private void addHyperlinkListener(TextViewer viewer) { PresentationReconciler reconciler = new PresentationReconciler(); RichTextDamagerRepairer dr = new RichTextDamagerRepairer( new RichTextScanner()); reconciler.setDamager(dr, IDocument.DEFAULT_CONTENT_TYPE); reconciler.setRepairer(dr, IDocument.DEFAULT_CONTENT_TYPE); reconciler.setDocumentPartitioning( IDocumentExtension3.DEFAULT_PARTITIONING); reconciler.install(viewer); } protected void handleControlDispose(DisposeEvent e) { if (contributor != null) { contributor.dispose(); } if (contextMenu != null) { contextMenu.dispose(); contextMenu = null; } if (toolBarManager != null) { toolBarManager.dispose(); toolBarManager = null; } if (document != null) { unhookDocument(document); document = null; } } public Control getControl() { return control; } public Control getFocusControl() { return textViewer.getControl(); } public IRichDocument getDocument() { return document; } public TextViewer getTextViewer() { return textViewer; } protected void hookDocument(IRichDocument document) { } protected void unhookDocument(IRichDocument document) { } public Object getInput() { return document; } public void refresh() { updateToolBar(getSelection()); updateTextControl(); } public IRichTextActionBarContributor getContributor() { return contributor; } private boolean isViewerEditable() { return document != null && isEditable(); } private void updateToolBar(ISelection selection) { if (toolBarManager == null) return; ToolBar toolbar = toolBarManager.getControl(); if (toolbar != null && !toolbar.isDisposed()) { toolbar.setEnabled(isViewerEditable()); } if (contributor != null) { contributor.selectionChanged(selection, isViewerEditable()); } toolBarManager.update(false); } protected void updateTextControl() { textViewer.setEditable(isViewerEditable()); if (document != null) { textViewer.getTextWidget().setEnabled(true); } else { textViewer.getTextWidget().setEnabled(false); } } public void setInput(Object input) { if (input instanceof IRichDocument) { setDocument((IRichDocument) input); } else { setDocument(null); } } public void setDocument(IRichDocument document) { IRichDocument oldDocument = this.document; this.document = document; documentChanged(document, oldDocument); textViewer.setDocument(document == null ? new Document() : document); refresh(); // move the caret to the end of document if (document != null) { textViewer.setSelectedRange(document.getLength(), 0); } } protected void documentChanged(IRichDocument newDocument, IRichDocument oldDocument) { if (newDocument != oldDocument) { if (oldDocument != null) { unhookDocument(oldDocument); } if (newDocument != null) { hookDocument(newDocument); } } } public IRichTextRenderer getRenderer() { return renderer; } public ISelection getSelection() { return textViewer.getSelection(); } public Point getSelectedRange() { return textViewer.getSelectedRange(); } public void setSelection(ISelection selection) { textViewer.setSelection(selection); } public void setSelection(ISelection selection, boolean reveal) { textViewer.setSelection(selection, reveal); } public void setSelectedRange(int selectionOffset, int selectionLength) { textViewer.setSelectedRange(selectionOffset, selectionLength); } public void addSelectionChangedListener( ISelectionChangedListener listener) { textViewer.addSelectionChangedListener(listener); } public void removeSelectionChangedListener( ISelectionChangedListener listener) { textViewer.removeSelectionChangedListener(listener); } public void addPostSelectionChangedListener( ISelectionChangedListener listener) { textViewer.addPostSelectionChangedListener(listener); } public void removePostSelectionChangedListener( ISelectionChangedListener listener) { textViewer.removePostSelectionChangedListener(listener); } public void addTextInputListener(ITextInputListener listener) { textViewer.addTextInputListener(listener); } public void addTextListener(ITextListener listener) { textViewer.addTextListener(listener); } public void removeTextInputListener(ITextInputListener listener) { textViewer.removeTextInputListener(listener); } public void removeTextListener(ITextListener listener) { textViewer.removeTextListener(listener); } public boolean isSelectedRangeEmpty() { Point p = getSelectedRange(); return p.y <= 0; } public StyledText getTextWidget() { return textViewer.getTextWidget(); } public boolean isEditable() { return editable; } public void setEditable(boolean editable) { if (editable == this.editable) return; this.editable = editable; refresh(); } }