| /******************************************************************************* |
| * Copyright (c) 2004, 2008 Tasktop Technologies 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: |
| * Tasktop Technologies - initial API and implementation |
| * Raphael Ackermann - spell checking support on bug 195514 |
| * Jingwen Ou - extensibility improvements |
| * David Green - fix for bug 256702 |
| *******************************************************************************/ |
| |
| package org.eclipse.mylyn.internal.tasks.ui.editors; |
| |
| import java.util.Iterator; |
| |
| import org.eclipse.core.runtime.Assert; |
| import org.eclipse.jface.action.Action; |
| import org.eclipse.jface.action.IAction; |
| import org.eclipse.jface.resource.JFaceResources; |
| import org.eclipse.jface.text.Document; |
| import org.eclipse.jface.text.ITextListener; |
| import org.eclipse.jface.text.TextEvent; |
| import org.eclipse.jface.text.source.AnnotationModel; |
| import org.eclipse.jface.text.source.IAnnotationAccess; |
| import org.eclipse.jface.text.source.SourceViewer; |
| import org.eclipse.mylyn.internal.provisional.commons.ui.CommonTextSupport; |
| import org.eclipse.mylyn.internal.provisional.commons.ui.CommonThemes; |
| import org.eclipse.mylyn.internal.tasks.ui.commands.ViewSourceHandler; |
| import org.eclipse.mylyn.internal.tasks.ui.editors.RepositoryTextViewerConfiguration.Mode; |
| import org.eclipse.mylyn.internal.tasks.ui.util.TasksUiInternal; |
| import org.eclipse.mylyn.tasks.core.TaskRepository; |
| import org.eclipse.mylyn.tasks.core.data.TaskAttribute; |
| import org.eclipse.mylyn.tasks.core.data.TaskDataModel; |
| import org.eclipse.mylyn.tasks.ui.editors.AbstractAttributeEditor; |
| import org.eclipse.mylyn.tasks.ui.editors.AbstractRenderingEngine; |
| import org.eclipse.mylyn.tasks.ui.editors.AbstractTaskEditorExtension; |
| import org.eclipse.mylyn.tasks.ui.editors.LayoutHint; |
| import org.eclipse.mylyn.tasks.ui.editors.LayoutHint.ColumnSpan; |
| import org.eclipse.mylyn.tasks.ui.editors.LayoutHint.RowSpan; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.custom.StackLayout; |
| import org.eclipse.swt.events.DisposeEvent; |
| import org.eclipse.swt.events.DisposeListener; |
| import org.eclipse.swt.events.FocusAdapter; |
| import org.eclipse.swt.events.FocusEvent; |
| import org.eclipse.swt.events.FocusListener; |
| import org.eclipse.swt.graphics.Font; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.swt.widgets.Event; |
| import org.eclipse.swt.widgets.Listener; |
| import org.eclipse.swt.widgets.Menu; |
| import org.eclipse.ui.PlatformUI; |
| import org.eclipse.ui.contexts.IContextActivation; |
| import org.eclipse.ui.contexts.IContextService; |
| import org.eclipse.ui.editors.text.EditorsUI; |
| import org.eclipse.ui.forms.widgets.FormToolkit; |
| import org.eclipse.ui.texteditor.AnnotationPreference; |
| import org.eclipse.ui.texteditor.DefaultMarkerAnnotationAccess; |
| import org.eclipse.ui.texteditor.MarkerAnnotationPreferences; |
| import org.eclipse.ui.texteditor.SourceViewerDecorationSupport; |
| import org.eclipse.ui.themes.IThemeManager; |
| |
| /** |
| * A text attribute editor that can switch between a editor, preview and source view. |
| * |
| * @author Raphael Ackermann |
| * @author Steffen Pingel |
| * @author Jingwen Ou |
| */ |
| public class RichTextAttributeEditor extends AbstractAttributeEditor { |
| |
| private IContextActivation contextActivation; |
| |
| private final IContextService contextService; |
| |
| private SourceViewer defaultViewer; |
| |
| private Composite editorComposite; |
| |
| private StackLayout editorLayout; |
| |
| private final AbstractTaskEditorExtension extension; |
| |
| private SourceViewer editorViewer; |
| |
| private SourceViewer previewViewer; |
| |
| private final TaskRepository taskRepository; |
| |
| private FormToolkit toolkit; |
| |
| public class ViewSourceAction extends Action { |
| |
| public ViewSourceAction() { |
| super(Messages.RichTextAttributeEditor_Viewer_Source, SWT.TOGGLE); |
| setChecked(false); |
| setEnabled(false); |
| } |
| |
| @Override |
| public void run() { |
| if (isChecked()) { |
| showDefault(); |
| } else { |
| showEditor(); |
| } |
| if (editorLayout != null) { |
| EditorUtil.reflow(editorLayout.topControl); |
| } |
| ViewSourceHandler.setChecked(isChecked()); |
| } |
| |
| } |
| |
| private final IAction viewSourceAction = new ViewSourceAction(); |
| |
| private boolean spellCheckingEnabled; |
| |
| private final int style; |
| |
| private Mode mode; |
| |
| private AbstractRenderingEngine renderingEngine; |
| |
| private BrowserPreviewViewer browserViewer; |
| |
| public RichTextAttributeEditor(TaskDataModel manager, TaskRepository taskRepository, TaskAttribute taskAttribute) { |
| this(manager, taskRepository, taskAttribute, SWT.MULTI); |
| } |
| |
| public RichTextAttributeEditor(TaskDataModel manager, TaskRepository taskRepository, TaskAttribute taskAttribute, |
| int style) { |
| this(manager, taskRepository, taskAttribute, style, null, null); |
| } |
| |
| public RichTextAttributeEditor(TaskDataModel manager, TaskRepository taskRepository, TaskAttribute taskAttribute, |
| int style, IContextService contextService, AbstractTaskEditorExtension extension) { |
| super(manager, taskAttribute); |
| this.taskRepository = taskRepository; |
| this.style = style; |
| this.contextService = contextService; |
| this.extension = extension; |
| if ((style & SWT.MULTI) != 0) { |
| setLayoutHint(new LayoutHint(RowSpan.MULTIPLE, ColumnSpan.MULTIPLE)); |
| } else { |
| setLayoutHint(new LayoutHint(RowSpan.SINGLE, ColumnSpan.MULTIPLE)); |
| } |
| setMode(Mode.DEFAULT); |
| } |
| |
| public Mode getMode() { |
| return mode; |
| } |
| |
| public void setMode(Mode mode) { |
| Assert.isNotNull(mode); |
| this.mode = mode; |
| } |
| |
| private void installListeners(final SourceViewer viewer) { |
| viewer.addTextListener(new ITextListener() { |
| public void textChanged(TextEvent event) { |
| // filter out events caused by text presentation changes, e.g. annotation drawing |
| String value = viewer.getTextWidget().getText(); |
| if (!getValue().equals(value)) { |
| setValue(value); |
| EditorUtil.ensureVisible(viewer.getTextWidget()); |
| } |
| } |
| }); |
| // ensure that tab traverses to next control instead of inserting a tab character unless editing multi-line text |
| if ((style & SWT.MULTI) != 0 && mode != Mode.DEFAULT) { |
| viewer.getTextWidget().addListener(SWT.Traverse, new Listener() { |
| public void handleEvent(Event event) { |
| switch (event.detail) { |
| case SWT.TRAVERSE_TAB_NEXT: |
| case SWT.TRAVERSE_TAB_PREVIOUS: |
| event.doit = true; |
| break; |
| } |
| } |
| }); |
| } |
| } |
| |
| public String getValue() { |
| return getAttributeMapper().getValue(getTaskAttribute()); |
| } |
| |
| public boolean isSpellCheckingEnabled() { |
| return spellCheckingEnabled; |
| } |
| |
| public void setSpellCheckingEnabled(boolean spellCheckingEnabled) { |
| this.spellCheckingEnabled = spellCheckingEnabled; |
| } |
| |
| public void setValue(String value) { |
| getAttributeMapper().setValue(getTaskAttribute(), value); |
| attributeChanged(); |
| } |
| |
| /** Configures annotation model for spell checking. */ |
| private void configureAsEditor(SourceViewer viewer, Document document) { |
| AnnotationModel annotationModel = new AnnotationModel(); |
| viewer.showAnnotations(false); |
| viewer.showAnnotationsOverview(false); |
| IAnnotationAccess annotationAccess = new DefaultMarkerAnnotationAccess(); |
| final SourceViewerDecorationSupport support = new SourceViewerDecorationSupport(viewer, null, annotationAccess, |
| EditorsUI.getSharedTextColors()); |
| Iterator<?> e = new MarkerAnnotationPreferences().getAnnotationPreferences().iterator(); |
| while (e.hasNext()) { |
| support.setAnnotationPreference((AnnotationPreference) e.next()); |
| } |
| support.install(EditorsUI.getPreferenceStore()); |
| viewer.getTextWidget().addDisposeListener(new DisposeListener() { |
| public void widgetDisposed(DisposeEvent e) { |
| support.uninstall(); |
| } |
| }); |
| //viewer.getTextWidget().setIndent(2); |
| viewer.setDocument(document, annotationModel); |
| } |
| |
| private RepositoryTextViewerConfiguration installHyperlinkPresenter(SourceViewer viewer) { |
| RepositoryTextViewerConfiguration configuration = new RepositoryTextViewerConfiguration(taskRepository, false); |
| configuration.setMode(getMode()); |
| |
| // do not configure viewer, this has already been done in extension |
| |
| AbstractHyperlinkTextPresentationManager manager; |
| if (getMode() == Mode.DEFAULT) { |
| manager = new HighlightingHyperlinkTextPresentationManager(); |
| manager.setHyperlinkDetectors(configuration.getDefaultHyperlinkDetectors(viewer, null)); |
| manager.install(viewer); |
| |
| manager = new TaskHyperlinkTextPresentationManager(); |
| manager.setHyperlinkDetectors(configuration.getDefaultHyperlinkDetectors(viewer, Mode.TASK)); |
| manager.install(viewer); |
| } else if (getMode() == Mode.TASK_RELATION) { |
| manager = new TaskHyperlinkTextPresentationManager(); |
| manager.setHyperlinkDetectors(configuration.getDefaultHyperlinkDetectors(viewer, Mode.TASK_RELATION)); |
| manager.install(viewer); |
| } |
| |
| return configuration; |
| } |
| |
| private SourceViewer configure(final SourceViewer viewer, boolean readOnly) { |
| // do this before setting the document to not require invalidating the presentation |
| installHyperlinkPresenter(viewer); |
| |
| Document document = new Document(getValue()); |
| if (readOnly) { |
| viewer.setDocument(document); |
| if (extension != null) { |
| // setting view source action |
| viewer.getControl().setData(ViewSourceHandler.VIEW_SOURCE_ACTION, viewSourceAction); |
| viewer.getControl().addFocusListener(new FocusAdapter() { |
| @Override |
| public void focusGained(FocusEvent e) { |
| ViewSourceHandler.setChecked(getViewer() == defaultViewer); |
| } |
| }); |
| } |
| } else { |
| configureAsEditor(viewer, document); |
| installListeners(viewer); |
| viewer.getControl().setData(FormToolkit.KEY_DRAW_BORDER, FormToolkit.TEXT_BORDER); |
| } |
| |
| // enable cut/copy/paste |
| CommonTextSupport.setTextViewer(viewer.getTextWidget(), viewer); |
| viewer.setEditable(!readOnly); |
| viewer.getTextWidget().setFont(getFont()); |
| toolkit.adapt(viewer.getControl(), false, false); |
| |
| return viewer; |
| } |
| |
| @Override |
| public void createControl(Composite parent, FormToolkit toolkit) { |
| this.toolkit = toolkit; |
| |
| int style = this.style; |
| if (!isReadOnly() && (style & TasksUiInternal.SWT_NO_SCROLL) == 0) { |
| style |= SWT.V_SCROLL; |
| } |
| |
| if (extension != null || renderingEngine != null) { |
| editorComposite = new Composite(parent, SWT.NULL); |
| editorLayout = new StackLayout() { |
| @Override |
| protected Point computeSize(Composite composite, int hint, int hint2, boolean flushCache) { |
| return topControl.computeSize(hint, hint2, flushCache); |
| } |
| }; |
| editorComposite.setLayout(editorLayout); |
| setControl(editorComposite); |
| |
| if (extension != null) { |
| if (isReadOnly()) { |
| editorViewer = extension.createViewer(taskRepository, editorComposite, style); |
| } else { |
| editorViewer = extension.createEditor(taskRepository, editorComposite, style); |
| editorViewer.getTextWidget().addFocusListener(new FocusListener() { |
| public void focusGained(FocusEvent e) { |
| setContext(); |
| } |
| |
| public void focusLost(FocusEvent e) { |
| unsetContext(); |
| } |
| }); |
| editorViewer.getTextWidget().addDisposeListener(new DisposeListener() { |
| public void widgetDisposed(DisposeEvent e) { |
| unsetContext(); |
| } |
| }); |
| } |
| configure(editorViewer, isReadOnly()); |
| show(editorViewer); |
| } else { |
| defaultViewer = createDefaultEditor(editorComposite, style); |
| configure(defaultViewer, isReadOnly()); |
| show(defaultViewer); |
| } |
| |
| viewSourceAction.setEnabled(true); |
| } else { |
| defaultViewer = createDefaultEditor(parent, style); |
| configure(defaultViewer, isReadOnly()); |
| setControl(defaultViewer.getControl()); |
| |
| viewSourceAction.setEnabled(false); |
| } |
| } |
| |
| private SourceViewer createDefaultEditor(Composite parent, int styles) { |
| SourceViewer defaultEditor = new SourceViewer(parent, null, styles | SWT.WRAP); |
| |
| RepositoryTextViewerConfiguration viewerConfig = new RepositoryTextViewerConfiguration(taskRepository, |
| isSpellCheckingEnabled() && !isReadOnly()); |
| viewerConfig.setMode(getMode()); |
| defaultEditor.configure(viewerConfig); |
| |
| return defaultEditor; |
| } |
| |
| public SourceViewer getDefaultViewer() { |
| if (defaultViewer == null) { |
| defaultViewer = createDefaultEditor(editorComposite, style); |
| configure(defaultViewer, isReadOnly()); |
| |
| // fixed font size |
| defaultViewer.getTextWidget().setFont(JFaceResources.getFontRegistry().get(JFaceResources.TEXT_FONT)); |
| // adapt maximize action |
| defaultViewer.getControl().setData(EditorUtil.KEY_TOGGLE_TO_MAXIMIZE_ACTION, |
| editorViewer.getControl().getData(EditorUtil.KEY_TOGGLE_TO_MAXIMIZE_ACTION)); |
| // adapt menu to the new viewer |
| installMenu(defaultViewer.getControl(), editorViewer.getControl().getMenu()); |
| } |
| return defaultViewer; |
| } |
| |
| private void installMenu(final Control control, Menu menu) { |
| if (menu != null) { |
| control.setMenu(menu); |
| control.addDisposeListener(new DisposeListener() { |
| public void widgetDisposed(DisposeEvent e) { |
| control.setMenu(null); |
| } |
| }); |
| } |
| } |
| |
| private Font getFont() { |
| IThemeManager themeManager = PlatformUI.getWorkbench().getThemeManager(); |
| Font font = themeManager.getCurrentTheme().getFontRegistry().get(CommonThemes.FONT_EDITOR_COMMENT); |
| return font; |
| } |
| |
| private SourceViewer getPreviewViewer() { |
| if (extension == null) { |
| return null; |
| } |
| |
| // construct as needed |
| if (previewViewer == null) { |
| // previewer should always have a vertical scroll bar if it's editable |
| int previewViewerStyle = style; |
| if (getEditorViewer() != null) { |
| previewViewerStyle |= SWT.V_SCROLL; |
| } |
| previewViewer = extension.createViewer(taskRepository, editorComposite, previewViewerStyle); |
| configure(previewViewer, true); |
| // adapt maximize action |
| previewViewer.getControl().setData(EditorUtil.KEY_TOGGLE_TO_MAXIMIZE_ACTION, |
| editorViewer.getControl().getData(EditorUtil.KEY_TOGGLE_TO_MAXIMIZE_ACTION)); |
| } |
| Document document = new Document(editorViewer.getDocument().get()); |
| previewViewer.setDocument(document); |
| return previewViewer; |
| } |
| |
| public SourceViewer getEditorViewer() { |
| return editorViewer; |
| } |
| |
| public SourceViewer getViewer() { |
| if (editorLayout == null) { |
| return defaultViewer; |
| } |
| if (defaultViewer != null && editorLayout.topControl == defaultViewer.getControl()) { |
| return defaultViewer; |
| } else if (previewViewer != null && editorLayout.topControl == previewViewer.getControl()) { |
| return previewViewer; |
| } else { |
| return editorViewer; |
| } |
| } |
| |
| private void setContext() { |
| if (contextService == null) { |
| return; |
| } |
| if (contextActivation != null) { |
| contextService.deactivateContext(contextActivation); |
| contextActivation = null; |
| } |
| if (contextService != null && extension.getEditorContextId() != null) { |
| contextActivation = contextService.activateContext(extension.getEditorContextId()); |
| } |
| } |
| |
| /** |
| * Brings <code>viewer</code> to top. |
| */ |
| private void show(SourceViewer viewer) { |
| show(viewer.getControl()); |
| } |
| |
| /** |
| * Brings <code>control</code> to top. |
| */ |
| private void show(Control control) { |
| // no extension is available |
| if (editorComposite == null) { |
| return; |
| } |
| |
| editorLayout.topControl = control; |
| if (editorComposite.getParent().getLayout() instanceof FillWidthLayout) { |
| ((FillWidthLayout) editorComposite.getParent().getLayout()).flush(); |
| } |
| editorComposite.layout(); |
| control.setFocus(); |
| } |
| |
| public void showDefault() { |
| show(getDefaultViewer()); |
| } |
| |
| public void showPreview() { |
| if (!isReadOnly()) { |
| show(getPreviewViewer()); |
| } |
| } |
| |
| public void showEditor() { |
| if (getEditorViewer() != null) { |
| show(getEditorViewer()); |
| } else { |
| show(getDefaultViewer()); |
| } |
| } |
| |
| private void unsetContext() { |
| if (contextService == null) { |
| return; |
| } |
| if (contextActivation != null) { |
| contextService.deactivateContext(contextActivation); |
| contextActivation = null; |
| } |
| } |
| |
| public boolean hasPreview() { |
| return extension != null && !isReadOnly(); |
| } |
| |
| public boolean hasBrowser() { |
| return renderingEngine != null; |
| } |
| |
| private BrowserPreviewViewer getBrowserViewer() { |
| if (editorComposite == null || renderingEngine == null) { |
| return null; |
| } |
| |
| if (browserViewer == null) { |
| browserViewer = new BrowserPreviewViewer(getModel().getTaskRepository(), renderingEngine); |
| browserViewer.createControl(editorComposite, toolkit); |
| } |
| return browserViewer; |
| } |
| |
| public AbstractRenderingEngine getRenderingEngine() { |
| return renderingEngine; |
| } |
| |
| public void setRenderingEngine(AbstractRenderingEngine renderingEngine) { |
| this.renderingEngine = renderingEngine; |
| } |
| |
| public void showBrowser() { |
| BrowserPreviewViewer viewer = getBrowserViewer(); |
| viewer.update(getValue()); |
| if (viewer != null) { |
| show(viewer.getControl()); |
| } |
| } |
| |
| public IAction getViewSourceAction() { |
| return viewSourceAction; |
| } |
| |
| } |