/******************************************************************************* * Copyright (c) 2007, 2018 Dakshinamurthy Karra, IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Dakshinamurthy Karra (Jalian Systems) - Templates View - https://bugs.eclipse.org/bugs/show_bug.cgi?id=69581 * Piotr Maj - no access to template store and current selection - https://bugs.eclipse.org/bugs/show_bug.cgi?id=296439 *******************************************************************************/ package org.eclipse.ui.texteditor.templates; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import com.ibm.icu.text.Collator; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.CLabel; import org.eclipse.swt.custom.SashForm; import org.eclipse.swt.custom.StyledText; import org.eclipse.swt.custom.ViewForm; import org.eclipse.swt.dnd.Clipboard; import org.eclipse.swt.dnd.DND; import org.eclipse.swt.dnd.DragSourceAdapter; import org.eclipse.swt.dnd.DragSourceEvent; import org.eclipse.swt.dnd.DropTarget; import org.eclipse.swt.dnd.DropTargetAdapter; import org.eclipse.swt.dnd.DropTargetEvent; import org.eclipse.swt.dnd.TextTransfer; import org.eclipse.swt.dnd.Transfer; import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.events.ControlListener; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Tree; import org.eclipse.swt.widgets.TreeColumn; import org.eclipse.swt.widgets.TreeItem; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.IAction; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.action.IToolBarManager; import org.eclipse.jface.action.MenuManager; import org.eclipse.jface.action.Separator; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.layout.PixelConverter; import org.eclipse.jface.layout.TreeColumnLayout; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.viewers.AbstractTreeViewer; import org.eclipse.jface.viewers.ColumnPixelData; import org.eclipse.jface.viewers.IPostSelectionProvider; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.ISelectionProvider; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.ITableLabelProvider; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerComparator; import org.eclipse.jface.window.Window; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.Document; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextViewerExtension5; import org.eclipse.jface.text.source.ISourceViewer; import org.eclipse.jface.text.source.SourceViewer; import org.eclipse.jface.text.source.SourceViewerConfiguration; import org.eclipse.jface.text.templates.ContextTypeRegistry; import org.eclipse.jface.text.templates.Template; import org.eclipse.jface.text.templates.TemplateContextType; import org.eclipse.jface.text.templates.persistence.TemplatePersistenceData; import org.eclipse.jface.text.templates.persistence.TemplateStore; import org.eclipse.ui.IActionBars; import org.eclipse.ui.IWorkbenchActionConstants; import org.eclipse.ui.IWorkbenchCommandConstants; import org.eclipse.ui.actions.ActionFactory; import org.eclipse.ui.dialogs.PreferencesUtil; import org.eclipse.ui.internal.texteditor.NLSUtility; import org.eclipse.ui.internal.texteditor.TextEditorPlugin; import org.eclipse.ui.part.Page; import org.eclipse.ui.texteditor.ITextEditor; import org.eclipse.ui.texteditor.ITextEditorActionConstants; import org.eclipse.ui.texteditor.ITextEditorExtension2; import org.eclipse.ui.texteditor.templates.TemplatePreferencePage.EditTemplateDialog; /** * Abstract default implementation for {@link ITemplatesPage}. *

* Clients who are defining an editor may elect to provide a corresponding * templates page. This templates page will be presented to the user via the * Templates View (the user decides whether their workbench window contains this * view) whenever that editor is active. This class should be subclassed by * clients. *

*

* Internally, a AbstractTemplatesPage uses the template store to display different * categories. A link to editor mode on the templates page allows to filtering * of the categories to only that are supported in this context. *

* * @since 3.4 */ public abstract class AbstractTemplatesPage extends Page implements ITemplatesPage, ITemplatesPageExtension { /** * Sashform size */ private static final String SASH_SIZE_PREF_ID= TextEditorPlugin.PLUGIN_ID + ".templates.templatesPage.sashSize"; //$NON-NLS-1$ /** * Tree columns widths */ private static final String COLUMN_NAME_WIDTH_PREF_ID= TextEditorPlugin.PLUGIN_ID + ".templates.templatesPage.nameWidth"; //$NON-NLS-1$ private static final String COLUMN_DESCRIPTION_WIDTH_PREF_ID= TextEditorPlugin.PLUGIN_ID + ".templates.templatesPage.descriptionWidth"; //$NON-NLS-1$ /** * Link to editor action setting */ private static final String LINK_ACTION_PREF_ID= TextEditorPlugin.PLUGIN_ID + ".templates.templatesPage.linkAction"; //$NON-NLS-1$ /** * Context expand/collapse setting prefix */ private static final String CONTEXT_COLLAPSE_PREF_ID= TextEditorPlugin.PLUGIN_ID + "templates.templatesPage.context.expand."; //$NON-NLS-1$ /** * The ID for the popup menu for this templates page */ private static final String POPUP_MENU_ID= "org.eclipse.ui.texteditor.templates.PopupMenu"; //$NON-NLS-1$ /** * A post selection changed listener for the editor. Depending on the caret * position updates the templates */ private final class SelectionChangedListener implements ISelectionChangedListener { @Override public void selectionChanged(SelectionChangedEvent event) { String[] contextTypes= getEditorContextTypeIds(); if (needUpdate(contextTypes)) { fCurrentContextTypeIds= contextTypes; updateContextTypes(fCurrentContextTypeIds); return; } } /** * Check whether an update of the AbstractTemplatesPage is needed * * @param contextTypes the context types * @return true if update is needed */ private boolean needUpdate(String[] contextTypes) { return fCurrentContextTypeIds == null || fCurrentContextTypeIds.length != contextTypes.length || contextTypeChanged(contextTypes); } /** * Check whether there is any change in the context types needed * * @param contextTypes the context types * @return true if any of the context types changed */ private boolean contextTypeChanged(String[] contextTypes) { for (int i= 0; i < contextTypes.length; i++) { if (!contextTypes[i].equals(fCurrentContextTypeIds[i])) return true; } return false; } } /** * Drop support for the editor linked to this page. When a user drops a * template into the active editor, the template is applied at the drop * position. */ private final class EditorDropTargetListener extends DropTargetAdapter { @Override public void dragEnter(DropTargetEvent event) { if (!TemplatesTransfer.getInstance().isSupportedType(event.currentDataType)) return; event.detail= DND.DROP_COPY; } @Override public void dragOperationChanged(DropTargetEvent event) { if (!TemplatesTransfer.getInstance().isSupportedType(event.currentDataType)) return; event.detail= DND.DROP_COPY; } @Override public void dragOver(DropTargetEvent event) { if (!TemplatesTransfer.getInstance().isSupportedType(event.currentDataType)) return; event.feedback |= DND.FEEDBACK_SCROLL | DND.FEEDBACK_SELECT; event.detail= DND.DROP_NONE; TemplatePersistenceData[] selectedTemplates= getSelectedTemplates(); if (fTextEditor instanceof ITextEditorExtension2 && ((ITextEditorExtension2)fTextEditor).isEditorInputModifiable() && selectedTemplates.length == 1 && isTemplateValidAtLocation(selectedTemplates[0].getTemplate(), new Point(event.x, event.y))) event.detail= DND.DROP_COPY; } @Override public void drop(DropTargetEvent event) { if (!TemplatesTransfer.getInstance().isSupportedType(event.currentDataType)) return; TemplatePersistenceData[] selectedTemplates= getSelectedTemplates(); insertTemplate(selectedTemplates[0].getTemplate()); // The highlight of the item is removed once the drop happens - // restore it fTreeViewer.setSelection(new StructuredSelection(selectedTemplates), true); } } /** * Comparator for the viewer. Sorts the templates by name and then * description and context types by names. */ private static final class TemplateViewerComparator extends ViewerComparator { @Override public int compare(Viewer viewer, Object object1, Object object2) { if ((object1 instanceof TemplatePersistenceData) && (object2 instanceof TemplatePersistenceData)) { Template left= ((TemplatePersistenceData) object1).getTemplate(); Template right= ((TemplatePersistenceData) object2).getTemplate(); int result= Collator.getInstance().compare(left.getName(), right.getName()); if (result != 0) return result; return Collator.getInstance() .compare(left.getDescription(), right.getDescription()); } if ((object1 instanceof TemplateContextType) && (object2 instanceof TemplateContextType)) { return Collator.getInstance().compare(((TemplateContextType) object1).getName(), ((TemplateContextType) object2).getName()); } return super.compare(viewer, object1, object2); } @Override public boolean isSorterProperty(Object element, String property) { return false; } } /** * Label provider for templates. */ private final class TemplateLabelProvider extends LabelProvider implements ITableLabelProvider { @Override public Image getColumnImage(Object element, int columnIndex) { if (columnIndex != 0) return null; if (element instanceof TemplateContextType) return TemplatesPageImages.get(TemplatesPageImages.IMG_OBJ_CONTEXT); return AbstractTemplatesPage.this.getImage(((TemplatePersistenceData) element).getTemplate()); } @Override public String getColumnText(Object element, int columnIndex) { if (element instanceof TemplatePersistenceData) return getTemplateColumnText((TemplatePersistenceData) element, columnIndex); return getContextColumnText((TemplateContextType) element, columnIndex); } private String getTemplateColumnText(TemplatePersistenceData data, int columnIndex) { switch (columnIndex) { case 0: return data.getTemplate().getName(); case 1: return data.getTemplate().getDescription(); default: return ""; //$NON-NLS-1$ } } private String getContextColumnText(TemplateContextType contextType, int columnIndex) { switch (columnIndex) { case 0: return contextType.getName(); default: return ""; //$NON-NLS-1$ } } } /** * Content provider for templates. Provides all the enabled templates * defined for this editor. */ private final class TemplatesContentProvider implements ITreeContentProvider { @Override public Object[] getChildren(Object parentElement) { if (parentElement instanceof TemplatePersistenceData) return new Object[0]; else if (parentElement instanceof TemplateContextType) { TemplateContextType contextType= (TemplateContextType) parentElement; return getTemplates(contextType.getId()); } return null; } private TemplatePersistenceData[] getTemplates(String contextId) { List templateList= new ArrayList<>(); TemplatePersistenceData[] datas= getTemplateStore().getTemplateData(false); for (int i= 0; i < datas.length; i++) { if (datas[i].isEnabled() && datas[i].getTemplate().getContextTypeId().equals(contextId)) templateList.add(datas[i]); } return templateList .toArray(new TemplatePersistenceData[templateList.size()]); } @Override public Object getParent(Object element) { if (element instanceof TemplatePersistenceData) { TemplatePersistenceData templateData= (TemplatePersistenceData) element; return getContextTypeRegistry().getContextType( templateData.getTemplate().getContextTypeId()); } return null; } @Override public boolean hasChildren(Object parentElement) { if (parentElement instanceof TemplatePersistenceData) return false; else if (parentElement instanceof TemplateContextType) { String contextId= ((TemplateContextType) parentElement).getId(); TemplatePersistenceData[] datas= getTemplateStore().getTemplateData(false); if (datas.length <= 0) return false; for (int i= 0; i < datas.length; i++) { if (datas[i].isEnabled() && datas[i].getTemplate().getContextTypeId().equals(contextId)) return true; } return false; } return false; } @Override public Object[] getElements(Object inputElement) { List contextTypes= new ArrayList<>(); for (Iterator iterator= getContextTypeRegistry().contextTypes(); iterator.hasNext();) { TemplateContextType contextType= iterator.next(); if (!fLinkWithEditorAction.isChecked() || isActiveContext(contextType)) contextTypes.add(contextType); } return contextTypes.toArray(new TemplateContextType[contextTypes.size()]); } private boolean isActiveContext(TemplateContextType contextType) { return fActiveTypes == null || fActiveTypes.contains(contextType.getId()); } @Override public void dispose() { } @Override public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { } } /** The text editor. */ private final ITextEditor fTextEditor; /** The source viewer attached to this editor. */ private final ISourceViewer fViewer; /* Listener to monitor changes to template store */ private IPropertyChangeListener fTemplateChangeListener; /* Control for this pagebook view */ private SashForm fControl; /* Actions */ private Action fInsertAction; private Action fAddAction; private Action fEditAction; private Action fRemoveAction; private Action fLinkWithEditorAction; private Action fCollapseAllAction; private Action fPreferencePageAction; /* Clipboard actions */ private Action fPasteAction; private Action fCopyAction; /* Current active context types for the editor */ private List fActiveTypes; /* Preference stores */ private IPreferenceStore fPreferenceStore; /* Controls */ private Tree fTemplatesTree; private TreeViewer fTreeViewer; private Menu fContextMenu; /** Current selection. */ private TemplatePersistenceData[] fSelectedTemplates= new TemplatePersistenceData[0]; /** The pattern viewer to be used with this view. */ private SourceViewer fPatternViewer; /* Cached results for avoiding processing while drag-over the editor. */ private int fCachedOffset; private boolean fCachedResult; private Point fCachedPosition; /** The current context type ids. */ private String[] fCurrentContextTypeIds; /** The selection changed listener to monitor the editor selections. */ private SelectionChangedListener fSelectionChangedListener; /** Paste action support for the editor. */ private IAction fEditorOldPasteAction; private IAction fEditorPasteAction; /** * Creates a new templates page. * * @param editor the editor * @param viewer the source viewer */ protected AbstractTemplatesPage(ITextEditor editor, ISourceViewer viewer) { Assert.isLegal(editor != null); Assert.isLegal(viewer != null); fTextEditor= editor; fViewer= viewer; setupPreferenceStore(); setupEditorDropTarget(); setupSelectionProvider(); setupPasteOperation(); } @Override public void createControl(Composite ancestor) { setupActions(); fControl= new SashForm(ancestor, SWT.VERTICAL); createTemplateTree(fControl); createPatternForm(fControl); hookContextMenu(); initializeDND(); updateButtons(); int sashSize= fPreferenceStore.getInt(SASH_SIZE_PREF_ID); fControl.setWeights(new int[] { sashSize, 100 - sashSize }); fTemplateChangeListener = event -> getShell().getDisplay().asyncExec(() -> refresh()); getTemplatePreferenceStore().addPropertyChangeListener(fTemplateChangeListener); updateContextTypes(getEditorContextTypeIds()); } @Override public void setFocus() { } @Override public Control getControl() { return fControl; } @Override public void dispose() { ISelectionProvider selectionProvider= fViewer.getSelectionProvider(); if (selectionProvider instanceof IPostSelectionProvider) ((IPostSelectionProvider) selectionProvider).removePostSelectionChangedListener(fSelectionChangedListener); else selectionProvider.removeSelectionChangedListener(fSelectionChangedListener); fTextEditor.setAction(ITextEditorActionConstants.PASTE, fEditorOldPasteAction); if (fContextMenu != null && !fContextMenu.isDisposed()) fContextMenu.dispose(); if (fTemplateChangeListener != null) getTemplatePreferenceStore().removePropertyChangeListener(fTemplateChangeListener); super.dispose(); } /** * Returns the shell in which this page is displayed. * * @return the shell */ private Shell getShell() { return getSite().getShell(); } /** * Returns the image to be used for the given template. *

* Clients can override to provide a different image.

* * @param template the template * @return the image, must not be disposed */ protected Image getImage(Template template) { return TemplatesPageImages.get(TemplatesPageImages.IMG_OBJ_TEMPLATE); } /** * Creates and opens a dialog to edit the given template. *

* * @param template the template being edited * @param edit true if the dialog allows editing * @param isNameModifiable true if the template name may be modified * @return the created or modified template, or null if the editing failed */ protected Template editTemplate(Template template, boolean edit, boolean isNameModifiable) { EditTemplateDialog dialog= new EditTemplateDialog(getShell(), template, edit, isNameModifiable, getContextTypeRegistry()); if (dialog.open() == Window.OK) return dialog.getTemplate(); return null; } /** * Update the pattern viewer to show the current template. *

* Subclasses can extend this method to update their own pattern viewer. *

* * @param template the template */ protected void updatePatternViewer(Template template) { String pattern= template != null ? template.getPattern() : ""; //$NON-NLS-1$ fPatternViewer.getDocument().set(pattern); } /** * Creates, configures and returns a source viewer to present the template * pattern on the templates page. *

* Clients may override to provide a custom source viewer featuring e.g. syntax coloring.

* * @param parent the parent control * @return a configured source viewer */ protected SourceViewer createPatternViewer(Composite parent) { SourceViewer viewer= new SourceViewer(parent, null, null, false, SWT.V_SCROLL | SWT.H_SCROLL); SourceViewerConfiguration configuration= new SourceViewerConfiguration(); viewer.configure(configuration); IDocument document= new Document(); viewer.setDocument(document); viewer.setEditable(false); return viewer; } /** * Returns the pattern viewer created by createPatternViewer() * * @return the pattern viewer */ protected final SourceViewer getPatternViewer() { return fPatternViewer; } /** * The caret position in the editor has moved into a new context type. It is * the subclasses responsibility to see that this is called only when needed * by keeping track of editor contents (eg. partitions). * * @param ids the ids */ private void updateContextTypes(String[] ids) { fActiveTypes= Arrays.asList(ids); if (fLinkWithEditorAction != null && fLinkWithEditorAction.isChecked()) refresh(); } /** * Inserts the given template into the editor. * * @param template the template * @param document the document */ abstract protected void insertTemplate(Template template, IDocument document); /** * Returns the context type registry used in this page. * * @return the context type registry */ abstract protected ContextTypeRegistry getContextTypeRegistry(); /** * Returns the template store used in this page. * * @return the template store * @since 3.6 public, before it was protected */ @Override abstract public TemplateStore getTemplateStore(); /** * Returns the preference store used to create the template store returned by * {@link AbstractTemplatesPage#getTemplateStore()}. * * @return the preference store */ abstract protected IPreferenceStore getTemplatePreferenceStore(); /** * Returns the Template preference page id to be used by this template page. * * @return id the preference page if or null if none exists */ abstract protected String getPreferencePageId(); /** * Returns the context type ids supported at the given document offset. * * @param document the document * @param offset the offset * @return an array of supported context ids */ protected abstract String[] getContextTypeIds(IDocument document, int offset); /** * Returns the context type ids supported at the current * caret position of the editor. * * @return an array with the the supported context type ids */ private String[] getEditorContextTypeIds() { Point selectedRange=fViewer.getSelectedRange(); int offset= selectedRange.x + selectedRange.y; IDocument document= fTextEditor.getDocumentProvider().getDocument(fTextEditor.getEditorInput()); return getContextTypeIds(document, offset); } /** * Checks whether the given template is valid for the document at the given * offset and length. * * @param document the document * @param template the template * @param offset the offset * @param length the length * @return true if the template is valid */ protected abstract boolean isValidTemplate(IDocument document, Template template, int offset, int length); /** * Setup the preference store */ private void setupPreferenceStore() { fPreferenceStore= TextEditorPlugin.getDefault().getPreferenceStore(); fPreferenceStore.setDefault(LINK_ACTION_PREF_ID, true); fPreferenceStore.setDefault(SASH_SIZE_PREF_ID, 80); } /** * Setup the paste operation * * We get the editors Paste operation and sets up a new operation that * checks for the clipboard contents for {@link TemplatesTransfer} data. */ private void setupPasteOperation() { fEditorOldPasteAction= fTextEditor.getAction(ITextEditorActionConstants.PASTE); fEditorPasteAction= new Action(TemplatesMessages.TemplatesPage_paste) { @Override public void run() { Clipboard clipboard= new Clipboard(getShell().getDisplay()); try { Template template= getTemplateFromClipboard(clipboard); if (template != null) insertTemplate(template); else fEditorOldPasteAction.run(); } finally { clipboard.dispose(); } } @Override public void runWithEvent(Event event) { run(); } /** * Convert the clipboard contents into a template * * @param clipboard the clipboard * @return the template or null if contents are not valid */ private Template getTemplateFromClipboard(Clipboard clipboard) { TemplatePersistenceData[] contents= (TemplatePersistenceData[])clipboard .getContents(TemplatesTransfer.getInstance()); if (contents != null && contents.length == 1) return contents[0].getTemplate(); return null; } }; fEditorPasteAction.setActionDefinitionId(IWorkbenchCommandConstants.EDIT_PASTE); fTextEditor.setAction(ITextEditorActionConstants.PASTE, fEditorPasteAction); } /** * Setup a selection listener to monitor the editor */ private void setupSelectionProvider() { ISelectionProvider selectionProvider= fViewer.getSelectionProvider(); fSelectionChangedListener= new SelectionChangedListener(); if (selectionProvider instanceof IPostSelectionProvider) ((IPostSelectionProvider) selectionProvider) .addPostSelectionChangedListener(fSelectionChangedListener); else selectionProvider.addSelectionChangedListener(fSelectionChangedListener); } /** * Setup the editor site as a drop target. */ private void setupEditorDropTarget() { Control control= fTextEditor.getAdapter(Control.class); if (control == null) return; DropTarget dropTarget= (DropTarget)control.getData(DND.DROP_TARGET_KEY); if (dropTarget == null) dropTarget= new DropTarget(control, DND.DROP_COPY); Transfer[] currentTransfers= dropTarget.getTransfer(); int currentLength= currentTransfers.length; Transfer[] newTransfers= new Transfer[currentLength + 1]; System.arraycopy(currentTransfers, 0, newTransfers, 0, currentLength); newTransfers[currentLength]= TemplatesTransfer.getInstance(); dropTarget.setTransfer(newTransfers); EditorDropTargetListener editorDropTarget= new EditorDropTargetListener(); dropTarget.addDropListener(editorDropTarget); } /** * Setup the menu, context menu and toolbar actions. */ private void setupActions() { createActions(); IActionBars actionBars= getSite().getActionBars(); actionBars.setGlobalActionHandler(ActionFactory.PASTE.getId(), fPasteAction); fPasteAction.setActionDefinitionId(ActionFactory.PASTE.getCommandId()); fPasteAction.setText(TemplatesMessages.TemplatesPage_paste); actionBars.setGlobalActionHandler(ActionFactory.COPY.getId(), fCopyAction); fCopyAction.setActionDefinitionId(ActionFactory.COPY.getCommandId()); fCopyAction.setText(TemplatesMessages.TemplatesPage_copy); fillToolbar(actionBars); fillMenu(actionBars); } /** * Create all the actions */ private void createActions() { fInsertAction= new Action(TemplatesMessages.TemplatesPage_insert) { @Override public void run() { TemplatePersistenceData[] selectedTemplates= getSelectedTemplates(); insertTemplate(selectedTemplates[0].getTemplate()); } }; fInsertAction.setImageDescriptor(TemplatesPageImages .getDescriptor(TemplatesPageImages.IMG_ELCL_TEMPLATE_INSERT)); fInsertAction.setDisabledImageDescriptor(TemplatesPageImages .getDescriptor(TemplatesPageImages.IMG_DLCL_TEMPLATE_INSERT)); fInsertAction.setToolTipText(TemplatesMessages.TemplatesPage_insert_tooltip); fAddAction= new Action(TemplatesMessages.TemplatesPage_new) { @Override public void run() { addTemplate(); } }; fAddAction.setImageDescriptor(TemplatesPageImages .getDescriptor(TemplatesPageImages.IMG_ELCL_TEMPLATE_NEW)); fAddAction.setToolTipText(TemplatesMessages.TemplatesPage_new_tooltip); fEditAction= new Action(TemplatesMessages.TemplatesPage_edit) { @Override public void run() { editTemplate(); } }; fEditAction.setImageDescriptor(TemplatesPageImages .getDescriptor(TemplatesPageImages.IMG_ELCL_TEMPLATE_EDIT)); fEditAction.setDisabledImageDescriptor(TemplatesPageImages .getDescriptor(TemplatesPageImages.IMG_DLCL_TEMPLATE_EDIT)); fEditAction.setToolTipText(TemplatesMessages.TemplatesPage_edit_tooltip); fRemoveAction= new Action(TemplatesMessages.TemplatesPage_remove) { @Override public void run() { removeTemplates(); } }; fRemoveAction.setImageDescriptor(TemplatesPageImages .getDescriptor(TemplatesPageImages.IMG_DLCL_TEMPLATE_DELETE)); fRemoveAction.setImageDescriptor(TemplatesPageImages .getDescriptor(TemplatesPageImages.IMG_ELCL_TEMPLATE_DELETE)); fRemoveAction.setToolTipText(TemplatesMessages.TemplatesPage_remove_tooltip); fLinkWithEditorAction= new Action(TemplatesMessages.TemplatesPage_link_to_editor, IAction.AS_CHECK_BOX) { @Override public void run() { fPreferenceStore.setValue(LINK_ACTION_PREF_ID, fLinkWithEditorAction.isChecked()); refresh(); } }; fLinkWithEditorAction.setImageDescriptor(TemplatesPageImages .getDescriptor(TemplatesPageImages.IMG_ELCL_TEMPLATE_LINK)); fLinkWithEditorAction.setChecked(fPreferenceStore.getBoolean(LINK_ACTION_PREF_ID)); fLinkWithEditorAction .setToolTipText(TemplatesMessages.TemplatesPage_link_to_editor_tooltip); fCollapseAllAction= new Action(TemplatesMessages.TemplatesPage_collapse_all) { @Override public void run() { fTreeViewer.collapseAll(); } }; fCollapseAllAction.setImageDescriptor(TemplatesPageImages .getDescriptor(TemplatesPageImages.IMG_ELCL_TEMPLATE_COLLAPSE_ALL)); fCollapseAllAction.setToolTipText(TemplatesMessages.TemplatesPage_collapse_all_tooltip); if (getPreferencePageId() != null) { fPreferencePageAction= new Action(TemplatesMessages.TemplatesPage_preference_page) { @Override public void run() { showPreferencePage(); } }; fPreferencePageAction .setToolTipText(TemplatesMessages.TemplatesPage_preference_page_tooltip); } fPasteAction= new Action() { @Override public void run() { Clipboard clipboard= new Clipboard(getShell().getDisplay()); try { String pattern= ((String)clipboard.getContents(TextTransfer.getInstance())); if (pattern != null) { final Template template= new Template(createTemplateName(), TemplatesMessages.TemplatesPage_paste_description, getContextTypeId(), pattern.replaceAll("\\$", "\\$\\$"), true); //$NON-NLS-1$//$NON-NLS-2$ getShell().getDisplay().asyncExec(() -> addTemplate(template)); return; } TemplatePersistenceData[] templates= (TemplatePersistenceData[])clipboard.getContents(TemplatesTransfer.getInstance()); if (templates != null) copyTemplates(templates, getContextTypeId()); } finally { clipboard.dispose(); } } }; fCopyAction= new Action() { @Override public void run() { Clipboard clipboard= new Clipboard(getShell().getDisplay()); try { clipboard.setContents(new Object[] { getSelectedTemplates() }, new Transfer[] { TemplatesTransfer.getInstance() }); } finally { clipboard.dispose(); } } }; } /** * Inserts the given template into the editor. * * @param template the template */ private void insertTemplate(Template template) { IDocument document= fTextEditor.getDocumentProvider().getDocument(fTextEditor.getEditorInput()); insertTemplate(template, document); } /** * Fill the toolbar. * * @param actionBars the action bars */ private void fillToolbar(IActionBars actionBars) { IToolBarManager toolBarManager= actionBars.getToolBarManager(); toolBarManager.add(fInsertAction); toolBarManager.add(fAddAction); toolBarManager.add(fEditAction); toolBarManager.add(fRemoveAction); toolBarManager.add(new Separator()); toolBarManager.add(fLinkWithEditorAction); toolBarManager.add(fCollapseAllAction); } /** * Fill the view menu. * * @param actionBars the action bars */ private void fillMenu(IActionBars actionBars) { IMenuManager menuManager= actionBars.getMenuManager(); if (fPreferencePageAction != null) { menuManager.add(fPreferencePageAction); menuManager.add(new Separator()); } menuManager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS)); } /** * Fill the context menu items. * * @param manager the menu manager */ private void fillContextMenu(IMenuManager manager) { manager.add(fInsertAction); manager.add(new Separator()); manager.add(fAddAction); manager.add(fEditAction); manager.add(fRemoveAction); manager.add(new Separator()); manager.add(fCopyAction); manager.add(fPasteAction); // Other plug-ins can contribute there actions here manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS)); } /** * Create the tree to display templates. * * @param parent the parent composite */ private void createTemplateTree(Composite parent) { Composite treeComposite= new Composite(parent, SWT.NONE); GridData data= new GridData(GridData.FILL_BOTH); treeComposite.setLayoutData(data); TreeColumnLayout columnLayout= new TreeColumnLayout(); treeComposite.setLayout(columnLayout); fTemplatesTree= new Tree(treeComposite, SWT.H_SCROLL | SWT.V_SCROLL | SWT.MULTI | SWT.FULL_SELECTION); fTemplatesTree.setHeaderVisible(true); fTemplatesTree.setLinesVisible(true); PixelConverter pixelConverter= new PixelConverter(fTemplatesTree); TreeColumn columnName= new TreeColumn(fTemplatesTree, SWT.NONE); columnName.setText(TemplatesMessages.TemplatesPage_column_name); int minWidth= fPreferenceStore.getInt(COLUMN_NAME_WIDTH_PREF_ID); if (minWidth == 0) { minWidth= pixelConverter.convertWidthInCharsToPixels(30); } columnLayout.setColumnData(columnName, new ColumnPixelData(minWidth, true)); columnName.addControlListener(new ControlListener() { @Override public void controlMoved(ControlEvent e) { } @Override public void controlResized(ControlEvent e) { int nameWidth= ((TreeColumn) e.getSource()).getWidth(); fPreferenceStore.setValue(COLUMN_NAME_WIDTH_PREF_ID, nameWidth); } }); TreeColumn columnDescription= new TreeColumn(fTemplatesTree, SWT.NONE); columnDescription.setText(TemplatesMessages.TemplatesPage_column_description); minWidth= fPreferenceStore.getInt(COLUMN_DESCRIPTION_WIDTH_PREF_ID); if (minWidth == 0) { minWidth= pixelConverter.convertWidthInCharsToPixels(45); } columnLayout.setColumnData(columnDescription, new ColumnPixelData(minWidth, false)); columnDescription.addControlListener(new ControlListener() { @Override public void controlMoved(ControlEvent e) { } @Override public void controlResized(ControlEvent e) { int descriptionWidth= ((TreeColumn) e.getSource()).getWidth(); fPreferenceStore.setValue(COLUMN_DESCRIPTION_WIDTH_PREF_ID, descriptionWidth); } }); createTreeViewer(fTemplatesTree); } /** * Create the tree viewer and setup the providers. * * @param templatesTree the tree used to show the templates */ private void createTreeViewer(Tree templatesTree) { fTreeViewer= new TreeViewer(fTemplatesTree); fTreeViewer.setLabelProvider(new TemplateLabelProvider()); fTreeViewer.setContentProvider(new TemplatesContentProvider()); fTreeViewer.setComparator(new TemplateViewerComparator()); fTreeViewer.setInput(getTemplatePreferenceStore()); fTreeViewer.addDoubleClickListener(e -> { updateSelectedItems(); TemplatePersistenceData[] selectedTemplates = getSelectedTemplates(); if (selectedTemplates.length > 0) insertTemplate(selectedTemplates[0].getTemplate()); }); fTreeViewer.addSelectionChangedListener(e -> { updateSelectedItems(); updateButtons(); }); fTreeViewer.expandAll(); } /** * Setup the pattern viewer. * * @param parent the parent composite */ private void createPatternForm(Composite parent) { ViewForm viewForm= new ViewForm(parent, SWT.NONE); viewForm.setBorderVisible(false); CLabel previewLabel= new CLabel(viewForm, SWT.NONE); previewLabel.setText(TemplatesMessages.TemplatesPage_preview); previewLabel.setImage(TemplatesPageImages.get(TemplatesPageImages.IMG_OBJ_PREVIEW)); viewForm.setTopLeft(previewLabel); fPatternViewer= createPatternViewer(viewForm); viewForm.setContent(fPatternViewer.getControl()); viewForm.addControlListener(new ControlListener() { @Override public void controlMoved(ControlEvent e) { } @Override public void controlResized(ControlEvent e) { int[] weights= fControl.getWeights(); int sashSize= (int) (weights[0] * 100.0 / (weights[0] + weights[1])); fPreferenceStore.setValue(SASH_SIZE_PREF_ID, sashSize); } }); } /** * Hookup the context menu */ private void hookContextMenu() { MenuManager menuMgr= new MenuManager(POPUP_MENU_ID); menuMgr.setRemoveAllWhenShown(true); menuMgr.addMenuListener(manager -> fillContextMenu(manager)); fContextMenu= menuMgr.createContextMenu(fTreeViewer.getControl()); fTreeViewer.getControl().setMenu(fContextMenu); getSite().registerContextMenu(POPUP_MENU_ID, menuMgr, fTreeViewer); } /** * Check whether the template is valid for the given drop location. * * @param template the template * @param location the drop location * @return true if the template is valid */ private boolean isTemplateValidAtLocation(Template template, Point location) { StyledText textWidget= (StyledText) fTextEditor.getAdapter(Control.class); IDocument document= fTextEditor.getDocumentProvider().getDocument(fTextEditor.getEditorInput()); try { if (location.equals(fCachedPosition)) return fCachedResult; fCachedPosition= location; int offset= getOffset(document, textWidget, textWidget.toControl(location.x, location.y)); if (fCachedOffset == offset) return fCachedResult; fCachedOffset= offset; if (isValidTemplate(document, template, offset, 0)) return fCachedResult= true; } catch (BadLocationException e) { } return fCachedResult= false; } /** * Updates the selected items. */ private void updateSelectedItems() { setSelectedTemplates(); TemplatePersistenceData[] selectedTemplates= getSelectedTemplates(); if (selectedTemplates.length == 1) updatePatternViewer(selectedTemplates[0].getTemplate()); else updatePatternViewer(null); } /** * Shows the preference page. */ private void showPreferencePage() { PreferencesUtil.createPreferenceDialogOn(getShell(), getPreferencePageId(), null, null).open(); } /** * Update the state of the buttons */ private void updateButtons() { TemplatePersistenceData[] selectedTemplates= getSelectedTemplates(); fCopyAction.setEnabled(selectedTemplates.length > 0); fInsertAction.setEnabled(selectedTemplates.length == 1); fEditAction.setEnabled(selectedTemplates.length == 1); fRemoveAction.setEnabled(selectedTemplates.length > 0); } /** * Set the selected templates */ private void setSelectedTemplates() { IStructuredSelection selection = fTreeViewer.getStructuredSelection(); Iterator it= selection.iterator(); TemplatePersistenceData[] data= new TemplatePersistenceData[selection.size()]; int i= 0; while (it.hasNext()) { Object o= it.next(); if (o instanceof TemplatePersistenceData) data[i++]= (TemplatePersistenceData) o; else { fSelectedTemplates= new TemplatePersistenceData[0]; return; } } fSelectedTemplates= data; } /** * Returns the currently selected templates * * @return selected templates * @since 3.6 public, before it was private */ @Override public TemplatePersistenceData[] getSelectedTemplates() { return fSelectedTemplates; } /** * Add a template */ private void addTemplate() { String id= getContextTypeId(); if (id != null) { Template template= new Template("", "", id, "", true); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ addTemplate(template); } } /** * Returns the context type id of the selected template. * * @return the context type id of the selected template or the first from the * registry if no templates are selected */ private String getContextTypeId() { IStructuredSelection selection = fTreeViewer.getStructuredSelection(); Object item; if (selection.size() == 0) return getContextTypeRegistry().contextTypes().next().getId(); if (selection.size() == 1) { item= selection.getFirstElement(); if (item instanceof TemplatePersistenceData) return ((TemplatePersistenceData) item).getTemplate().getContextTypeId(); return ((TemplateContextType) item).getId(); } Iterator it= selection.iterator(); String contextId= null; while (it.hasNext()) { item= it.next(); if (contextId == null) contextId= getContextId(item); else if (!contextId.equals(getContextId(item))) return getContextTypeRegistry().contextTypes().next() .getId(); } return contextId; } /** * Returns the context id for the given item which is either a template or a context type. * * @param item the item * @return the context type id */ private String getContextId(Object item) { String contextId; if (item instanceof TemplatePersistenceData) contextId= ((TemplatePersistenceData) item).getTemplate().getContextTypeId(); else contextId= ((TemplateContextType) item).getId(); return contextId; } /** * Add a template. The dialog is filled with the values from the given template. * * @param template the template */ private void addTemplate(Template template) { Template newTemplate; newTemplate= editTemplate(template, false, true); if (newTemplate != null) { TemplatePersistenceData data= new TemplatePersistenceData(newTemplate, true); getTemplateStore().add(data); saveTemplateStore(); refresh(); fTreeViewer.setSelection(new StructuredSelection(data), true); } } /** * Save the template store */ private void saveTemplateStore() { try { getTemplateStore().save(); } catch (IOException e) { TextEditorPlugin.getDefault().getLog().log(new Status(IStatus.ERROR, TextEditorPlugin.PLUGIN_ID, TemplatesMessages.TemplatesPage_save_error_message, e)); MessageDialog.openError(getShell(), TemplatesMessages.TemplatesPage_save_error_message, e.getMessage()); } } /** * Edits the selected template. */ private void editTemplate() { TemplatePersistenceData selectedTemplate= getSelectedTemplates()[0]; Template oldTemplate= selectedTemplate.getTemplate(); Template newTemplate= editTemplate(new Template(oldTemplate), true, true); if (newTemplate != null) { if (!newTemplate.getName().equals(oldTemplate.getName()) && MessageDialog.openQuestion(getShell(), TemplatesMessages.TemplatesPage_question_create_new_title, TemplatesMessages.TemplatesPage_question_create_new_message)) { TemplatePersistenceData templateData= new TemplatePersistenceData(newTemplate, true); getTemplateStore().add(templateData); refresh(); fTreeViewer.setSelection(new StructuredSelection(templateData), true); } else { selectedTemplate.setTemplate(newTemplate); updatePatternViewer(newTemplate); } } saveTemplateStore(); } /** * Moves the selected template from one context to another. * * @param templates an array of template data * @param contextId the contextId * */ private void moveTemplates(TemplatePersistenceData[] templates, String contextId) { for (int i= 0; i < templates.length; i++) { Template t= templates[i].getTemplate(); templates[i].setTemplate(new Template(t.getName(), t.getDescription(), contextId, t .getPattern(), t.isAutoInsertable())); } saveTemplateStore(); fTreeViewer.setSelection(new StructuredSelection(templates), true); } /** * Copy the selected templates to another context * * @param templates an array of template data * @param contextId the context id * */ private void copyTemplates(TemplatePersistenceData[] templates, String contextId) { TemplatePersistenceData[] newTemplates= new TemplatePersistenceData[templates.length]; for (int i= 0; i < templates.length; i++) { Template t= templates[i].getTemplate(); newTemplates[i]= new TemplatePersistenceData(new Template(t.getName(), t .getDescription(), contextId, t.getPattern(), t.isAutoInsertable()), true); getTemplateStore().add(newTemplates[i]); } saveTemplateStore(); refresh(); fTreeViewer.setSelection(new StructuredSelection(newTemplates), true); } /** * Remove one or more selected templates */ private void removeTemplates() { String title; TemplatePersistenceData[] selectedTemplates= getSelectedTemplates(); if (selectedTemplates.length == 1) title= TemplatesMessages.TemplatesPage_remove_title_single; else title= TemplatesMessages.TemplatesPage_remove_title_multi; String message; if (selectedTemplates.length == 1) message= TemplatesMessages.TemplatesPage_remove_message_single; else message= NLSUtility.format(TemplatesMessages.TemplatesPage_remove_message_multi, new Object[] { Integer.valueOf(selectedTemplates.length) }); if (!MessageDialog.openQuestion(getShell(), title, message)) return; for (int i= 0; i < selectedTemplates.length; i++) { getTemplateStore().delete(selectedTemplates[i]); } saveTemplateStore(); fTreeViewer.setSelection(new StructuredSelection(new Object[] {}), true); } /** * Initializes drag and drop the template items */ private void initializeDND() { DragSourceAdapter dragListener= new DragSourceAdapter() { @Override public void dragStart(DragSourceEvent event) { if (getSelectedTemplates().length == 0) { event.doit= false; } } @Override public void dragSetData(DragSourceEvent event) { if (TemplatesTransfer.getInstance().isSupportedType(event.dataType)) { event.data= getSelectedTemplates(); } } }; fTreeViewer.addDragSupport(DND.DROP_COPY | DND.DROP_MOVE, new Transfer[] { TemplatesTransfer .getInstance() }, dragListener); DropTargetAdapter dropListener= new DropTargetAdapter() { Transfer textTransfer= TextTransfer.getInstance(); Transfer templateTransfer= TemplatesTransfer.getInstance(); @Override public void dragEnter(DropTargetEvent event) { if (event.detail == DND.DROP_DEFAULT) event.detail= DND.DROP_COPY; } @Override public void dragOperationChanged(DropTargetEvent event) { if (event.detail == DND.DROP_DEFAULT) event.detail= DND.DROP_COPY; } @Override public void dragOver(DropTargetEvent event) { event.feedback |= DND.FEEDBACK_SCROLL; if (event.item == null) { event.detail= DND.DROP_NONE; return; } int index= 0; boolean isTemplateTransfer= false; while (index < event.dataTypes.length) { if (textTransfer.isSupportedType(event.dataTypes[index])) { break; } if (templateTransfer.isSupportedType(event.dataTypes[index])) { isTemplateTransfer= true; break; } index++; } if (index < event.dataTypes.length) { event.currentDataType= event.dataTypes[index]; if (event.detail == DND.DROP_DEFAULT || !isTemplateTransfer) event.detail= DND.DROP_COPY; return; } } @Override public void drop(DropTargetEvent event) { if (event.item == null) return; Object object= ((TreeItem) event.item).getData(); final String contextId; if (object instanceof TemplateContextType) contextId= ((TemplateContextType) object).getId(); else contextId= ((TemplatePersistenceData) object).getTemplate().getContextTypeId(); if (textTransfer.isSupportedType(event.currentDataType)) { String text= ((String) event.data).replaceAll("\\$", "\\$\\$"); //$NON-NLS-1$ //$NON-NLS-2$ final Template template= new Template(createTemplateName(), TemplatesMessages.TemplatesPage_paste_description, contextId, text, true); getShell().getDisplay().asyncExec(() -> addTemplate(template)); return; } if (templateTransfer.isSupportedType(event.currentDataType)) { final TemplatePersistenceData[] templates= (TemplatePersistenceData[]) event.data; final int dropType= event.detail; getShell().getDisplay().asyncExec(() -> { if (dropType == DND.DROP_COPY) copyTemplates(templates, contextId); else moveTemplates(templates, contextId); }); } } }; Transfer[] transfers= new Transfer[] { TextTransfer.getInstance(), TemplatesTransfer.getInstance() }; fTreeViewer.addDropSupport(DND.DROP_COPY | DND.DROP_MOVE, transfers, dropListener); } /** * Create a template name. * * @return the created template name */ private String createTemplateName() { for (int i= 1; i < Integer.MAX_VALUE; i++) { String name= TemplatesMessages.TemplatesPage_snippet + i; if (getTemplateStore().findTemplate(name) == null) return name; } return null; } /** * Stores the collapse state of a context node. */ private void storeCollapseState() { TreeItem[] items= fTreeViewer.getTree().getItems(); for (int i= 0; i < items.length; i++) { fPreferenceStore.setValue(CONTEXT_COLLAPSE_PREF_ID + ((TemplateContextType) items[i].getData()).getId(), !items[i].getExpanded()); } } /** * Refreshes the template tree contents. */ private void refresh() { storeCollapseState(); fTreeViewer.getTree().setRedraw(false); try { fTreeViewer.refresh(); TreeItem[] items= fTreeViewer.getTree().getItems(); for (int i= 0; i < items.length; i++) { boolean isExpanded= !fPreferenceStore.getBoolean(CONTEXT_COLLAPSE_PREF_ID + ((TemplateContextType) items[i].getData()).getId()); if (isExpanded) fTreeViewer.expandToLevel(items[i].getData(), AbstractTreeViewer.ALL_LEVELS); else fTreeViewer.collapseToLevel(items[i].getData(), AbstractTreeViewer.ALL_LEVELS); } } finally { fTreeViewer.getTree().setRedraw(true); } } /** * Returns the document relative offset from the text widget relative point * * @param document the document * @param textWidget the text widget * @param point the point for which to get the offset * @return the offset * @throws BadLocationException if the document is accessed with an invalid line */ private int getOffset(IDocument document, StyledText textWidget, Point point) throws BadLocationException { int widgetCaret= fViewer.getTextWidget().getCaretOffset(); if (fViewer instanceof ITextViewerExtension5) { ITextViewerExtension5 ext= (ITextViewerExtension5) fViewer; int widgetOffset = textWidget.getOffsetAtPoint(point); int offset = widgetOffset != -1 ? ext.widgetOffset2ModelOffset(textWidget.getOffsetAtPoint(point)) : -1; if (offset == -1) { int docLineIndex= ext.widgetLine2ModelLine(textWidget.getLineIndex(point.y)); String lineDelimiter= document.getLineDelimiter(docLineIndex); int delimLength= lineDelimiter == null ? 0 : lineDelimiter.length(); return document.getLineOffset(docLineIndex) + document.getLineLength(docLineIndex) - delimLength; } return offset; } IRegion visible= fViewer.getVisibleRegion(); return widgetCaret + visible.getOffset(); } }