/******************************************************************************* * Copyright (c) 2009, 2017 xored software, Inc. * * 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: * xored software, Inc. - initial API and Implementation (Alex Panchenko) *******************************************************************************/ package org.eclipse.dltk.ui.preferences; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.eclipse.core.expressions.Expression; import org.eclipse.dltk.internal.ui.editor.ScriptSourceViewer; import org.eclipse.dltk.internal.ui.preferences.ScriptSourcePreviewerUpdater; import org.eclipse.dltk.internal.ui.util.SWTUtil; import org.eclipse.dltk.ui.DLTKUIPlugin; import org.eclipse.dltk.ui.IContextMenuConstants; import org.eclipse.dltk.ui.IDLTKUILanguageToolkit; import org.eclipse.dltk.ui.dialogs.StatusInfo; import org.eclipse.dltk.ui.text.ScriptSourceViewerConfiguration; import org.eclipse.dltk.ui.text.ScriptTextTools; import org.eclipse.dltk.ui.text.templates.TemplateVariableProcessor; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.GroupMarker; import org.eclipse.jface.action.IAction; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.action.MenuManager; import org.eclipse.jface.action.Separator; import org.eclipse.jface.commands.ActionHandler; import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.jface.dialogs.IDialogSettings; import org.eclipse.jface.dialogs.StatusDialog; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.resource.JFaceResources; 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.ITextOperationTarget; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.source.ISourceViewer; import org.eclipse.jface.text.source.SourceViewer; 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.TemplateException; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.StyledText; import org.eclipse.swt.events.FocusEvent; import org.eclipse.swt.events.FocusListener; import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Combo; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Text; import org.eclipse.swt.widgets.Widget; import org.eclipse.ui.ActiveShellExpression; import org.eclipse.ui.IWorkbenchCommandConstants; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.handlers.IHandlerActivation; import org.eclipse.ui.handlers.IHandlerService; import org.eclipse.ui.texteditor.ITextEditorActionConstants; import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds; import org.eclipse.ui.texteditor.IUpdate; /** * Dialog to edit a template. * <p> * <strong>Note:</strong> This is a copy of * org.eclipse.ui.texteditor.templates.TemplatePreferencePage.EditTemplateDialog * which we should try to eliminate (see: * https://bugs.eclipse.org/bugs/show_bug.cgi?id=208865). * </p> */ public class EditTemplateDialog extends StatusDialog { private static class TextViewerAction extends Action implements IUpdate { private int fOperationCode = -1; private ITextOperationTarget fOperationTarget; /** * Creates a new action. * * @param viewer * the viewer * @param operationCode * the opcode */ public TextViewerAction(ITextViewer viewer, int operationCode) { fOperationCode = operationCode; fOperationTarget = viewer.getTextOperationTarget(); update(); } /** * Updates the enabled state of the action. Fires a property change if * the enabled state changes. * * @see Action#firePropertyChange(String, Object, Object) */ @Override public void update() { // XXX: workaround for // https://bugs.eclipse.org/bugs/show_bug.cgi?id=206111 if (fOperationCode == ITextOperationTarget.REDO) return; boolean wasEnabled = isEnabled(); boolean isEnabled = (fOperationTarget != null && fOperationTarget.canDoOperation(fOperationCode)); setEnabled(isEnabled); if (wasEnabled != isEnabled) { firePropertyChange(ENABLED, wasEnabled ? Boolean.TRUE : Boolean.FALSE, isEnabled ? Boolean.TRUE : Boolean.FALSE); } } @Override public void run() { if (fOperationCode != -1 && fOperationTarget != null) { fOperationTarget.doOperation(fOperationCode); } } } private final IDLTKUILanguageToolkit toolkit; private Template fTemplate; private Text fNameText; private Text fDescriptionText; private Combo fContextCombo; private SourceViewer fPatternEditor; private Button fInsertVariableButton; private Button fAutoInsertCheckbox; private boolean fIsNameModifiable; private boolean fIsContextTypeModifiable; private StatusInfo fValidationStatus; private boolean fSuppressError = true; // https://bugs.eclipse.org/bugs/show_bug.cgi?id=4354 private Map<String, IAction> fGlobalActions = new HashMap<>(10); private List<String> fSelectionActions = new ArrayList<>(3); private String[][] fContextTypes; private ContextTypeRegistry fContextTypeRegistry; private final TemplateVariableProcessor fTemplateProcessor = new TemplateVariableProcessor(); /** * Creates a new dialog. * * @param parent * the shell parent of the dialog * @param template * the template to edit * @param edit * whether this is a new template or an existing being edited * @param isNameModifiable * whether the name of the template may be modified * @param isContextTypeModifiable * whether the context type of the template may be modified * @param registry * the context type registry to use */ public EditTemplateDialog(IDLTKUILanguageToolkit toolkit, Shell parent, Template template, boolean edit, boolean isNameModifiable, boolean isContextTypeModifiable, ContextTypeRegistry registry) { super(parent); this.toolkit = toolkit; String title = edit ? PreferencesMessages.EditTemplateDialog_title_edit : PreferencesMessages.EditTemplateDialog_title_new; setTitle(title); fTemplate = template; fIsNameModifiable = isNameModifiable; fIsContextTypeModifiable = isContextTypeModifiable; String delim = new Document().getLegalLineDelimiters()[0]; List<String[]> contexts = new ArrayList<>(); for (Iterator it = registry.contextTypes(); it.hasNext();) { TemplateContextType type = (TemplateContextType) it.next(); if (type.getId().equals("javadoc")) //$NON-NLS-1$ contexts.add(new String[] { type.getId(), type.getName(), "/**" + delim }); //$NON-NLS-1$ else contexts.add(0, new String[] { type.getId(), type.getName(), "" }); //$NON-NLS-1$ } fContextTypes = contexts.toArray(new String[contexts.size()][]); fValidationStatus = new StatusInfo(); fContextTypeRegistry = registry; TemplateContextType type = fContextTypeRegistry .getContextType(template.getContextTypeId()); fTemplateProcessor.setContextType(type); } /* * @see org.eclipse.jface.dialogs.Dialog#isResizable() * * @since 3.4 */ @Override protected boolean isResizable() { return true; } @Override public void create() { super.create(); updateStatusAndButtons(); getButton(IDialogConstants.OK_ID).setEnabled(getStatus().isOK()); } @Override protected Control createDialogArea(Composite ancestor) { Composite parent = new Composite(ancestor, SWT.NONE); GridLayout layout = new GridLayout(); layout.numColumns = 2; layout.marginHeight = convertVerticalDLUsToPixels( IDialogConstants.VERTICAL_MARGIN); layout.marginWidth = convertHorizontalDLUsToPixels( IDialogConstants.HORIZONTAL_MARGIN); layout.verticalSpacing = convertVerticalDLUsToPixels( IDialogConstants.VERTICAL_SPACING); layout.horizontalSpacing = convertHorizontalDLUsToPixels( IDialogConstants.HORIZONTAL_SPACING); parent.setLayout(layout); parent.setLayoutData(new GridData(GridData.FILL_BOTH)); ModifyListener listener = e -> doTextWidgetChanged(e.widget); if (fIsNameModifiable) { createLabel(parent, PreferencesMessages.EditTemplateDialog_name); Composite composite = new Composite(parent, SWT.NONE); composite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); layout = new GridLayout(); layout.numColumns = 4; layout.marginWidth = 0; layout.marginHeight = 0; composite.setLayout(layout); fNameText = createText(composite); fNameText.addFocusListener(new FocusListener() { @Override public void focusGained(FocusEvent e) { } @Override public void focusLost(FocusEvent e) { if (fSuppressError) { fSuppressError = false; updateStatusAndButtons(); } } }); if (fIsContextTypeModifiable) { createLabel(composite, PreferencesMessages.EditTemplateDialog_context); fContextCombo = new Combo(composite, SWT.READ_ONLY); SWTUtil.setDefaultVisibleItemCount(fContextCombo); for (int i = 0; i < fContextTypes.length; i++) { fContextCombo.add(fContextTypes[i][1]); } fContextCombo.addModifyListener(listener); // fAutoInsertCheckbox = createCheckbox(composite, // PreferencesMessages.EditTemplateDialog_autoinsert); // fAutoInsertCheckbox.setSelection(fTemplate.isAutoInsertable()); } } createLabel(parent, PreferencesMessages.EditTemplateDialog_description); int descFlags = fIsNameModifiable ? SWT.BORDER : SWT.BORDER | SWT.READ_ONLY; fDescriptionText = new Text(parent, descFlags); fDescriptionText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); fDescriptionText.addModifyListener(listener); Label patternLabel = createLabel(parent, PreferencesMessages.EditTemplateDialog_pattern); patternLabel .setLayoutData(new GridData(GridData.VERTICAL_ALIGN_BEGINNING)); fPatternEditor = createEditor(parent); Label filler = new Label(parent, SWT.NONE); filler.setLayoutData(new GridData()); Composite composite = new Composite(parent, SWT.NONE); layout = new GridLayout(); layout.marginWidth = 0; layout.marginHeight = 0; composite.setLayout(layout); composite.setLayoutData(new GridData()); fInsertVariableButton = new Button(composite, SWT.NONE); fInsertVariableButton.setLayoutData(getButtonGridData()); fInsertVariableButton.setText( PreferencesMessages.EditTemplateDialog_insert_variable); fInsertVariableButton.addSelectionListener(new SelectionListener() { @Override public void widgetSelected(SelectionEvent e) { fPatternEditor.getTextWidget().setFocus(); fPatternEditor .doOperation(ISourceViewer.CONTENTASSIST_PROPOSALS); } @Override public void widgetDefaultSelected(SelectionEvent e) { } }); fDescriptionText.setText(fTemplate.getDescription()); if (fIsNameModifiable) { fNameText.setText(fTemplate.getName()); fNameText.addModifyListener(listener); fContextCombo.select(getIndex(fTemplate.getContextTypeId())); } else { fPatternEditor.getControl().setFocus(); } initializeActions(); applyDialogFont(parent); return composite; } protected void doTextWidgetChanged(Widget w) { if (w == fNameText) { fSuppressError = false; updateStatusAndButtons(); } else if (w == fContextCombo) { String contextId = getContextId(); fTemplateProcessor.setContextType( fContextTypeRegistry.getContextType(contextId)); IDocument document = fPatternEditor.getDocument(); String prefix = getPrefix(); document.set(prefix + getPattern()); fPatternEditor.setVisibleRegion(prefix.length(), document.getLength() - prefix.length()); updateStatusAndButtons(); } else if (w == fDescriptionText) { // nothing } } private String getContextId() { if (fContextCombo != null && !fContextCombo.isDisposed()) { String name = fContextCombo.getText(); for (int i = 0; i < fContextTypes.length; i++) { if (name.equals(fContextTypes[i][1])) { return fContextTypes[i][0]; } } } return fTemplate.getContextTypeId(); } protected void doSourceChanged(IDocument document) { String text = document.get(); fValidationStatus.setOK(); TemplateContextType contextType = fContextTypeRegistry .getContextType(getContextId()); if (contextType != null) { try { contextType.validate(text); } catch (TemplateException e) { fValidationStatus.setError(e.getLocalizedMessage()); } } updateAction(ITextEditorActionConstants.UNDO); updateStatusAndButtons(); } private static GridData getButtonGridData() { GridData data = new GridData(GridData.FILL_HORIZONTAL); return data; } private static Label createLabel(Composite parent, String name) { Label label = new Label(parent, SWT.NULL); label.setText(name); label.setLayoutData(new GridData()); return label; } private static Text createText(Composite parent) { Text text = new Text(parent, SWT.BORDER); text.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); return text; } private SourceViewer createEditor(Composite parent) { String prefix = getPrefix(); IDocument document = new Document(prefix + fTemplate.getPattern()); final ScriptTextTools textTools = toolkit.getTextTools(); textTools.setupDocumentPartitioner(document); IPreferenceStore store = toolkit.getCombinedPreferenceStore(); SourceViewer viewer = new ScriptSourceViewer(parent, null, null, false, SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL, store); ScriptSourceViewerConfiguration configuration = textTools .createSourceViewerConfiguraton(store, null, fTemplateProcessor); viewer.configure(configuration); viewer.setEditable(true); viewer.setDocument(document, prefix.length(), document.getLength() - prefix.length()); Font font = JFaceResources .getFont(configuration.getFontPropertyPreferenceKey()); viewer.getTextWidget().setFont(font); new ScriptSourcePreviewerUpdater(viewer, configuration, store); int nLines = document.getNumberOfLines(); if (nLines < 5) { nLines = 5; } else if (nLines > 12) { nLines = 12; } Control control = viewer.getControl(); GridData data = new GridData(GridData.FILL_BOTH); data.widthHint = convertWidthInCharsToPixels(80); data.heightHint = convertHeightInCharsToPixels(nLines); control.setLayoutData(data); viewer.addTextListener(event -> { if (event.getDocumentEvent() != null) doSourceChanged(event.getDocumentEvent().getDocument()); }); viewer.addSelectionChangedListener( event -> updateSelectionDependentActions()); return viewer; } private String getPrefix() { String id = getContextId(); int idx = getIndex(id); if (idx != -1) return fContextTypes[idx][2]; else return ""; //$NON-NLS-1$ } private void initializeActions() { final List<IHandlerActivation> handlerActivations = new ArrayList<>(3); final IHandlerService handlerService = PlatformUI.getWorkbench() .getAdapter(IHandlerService.class); final Expression expression = new ActiveShellExpression( fPatternEditor.getControl().getShell()); getShell().addDisposeListener( e -> handlerService.deactivateHandlers(handlerActivations)); fPatternEditor.getTextWidget().addFocusListener(new FocusListener() { @Override public void focusLost(FocusEvent e) { handlerService.deactivateHandlers(handlerActivations); } @Override public void focusGained(FocusEvent e) { IAction action = fGlobalActions .get(ITextEditorActionConstants.REDO); handlerActivations.add(handlerService.activateHandler( IWorkbenchCommandConstants.EDIT_REDO, new ActionHandler(action), expression)); action = fGlobalActions.get(ITextEditorActionConstants.UNDO); handlerActivations.add(handlerService.activateHandler( IWorkbenchCommandConstants.EDIT_UNDO, new ActionHandler(action), expression)); action = fGlobalActions.get("ContentAssistProposal"); handlerActivations.add(handlerService.activateHandler( ITextEditorActionDefinitionIds.CONTENT_ASSIST_PROPOSALS, new ActionHandler(action), expression)); } }); TextViewerAction action = new TextViewerAction(fPatternEditor, ITextOperationTarget.UNDO); action.setText(PreferencesMessages.EditTemplateDialog_undo); fGlobalActions.put(ITextEditorActionConstants.UNDO, action); action = new TextViewerAction(fPatternEditor, ITextOperationTarget.REDO); action.setText(PreferencesMessages.EditTemplateDialog_redo); fGlobalActions.put(ITextEditorActionConstants.REDO, action); action = new TextViewerAction(fPatternEditor, ITextOperationTarget.CUT); action.setText(PreferencesMessages.EditTemplateDialog_cut); fGlobalActions.put(ITextEditorActionConstants.CUT, action); action = new TextViewerAction(fPatternEditor, ITextOperationTarget.COPY); action.setText(PreferencesMessages.EditTemplateDialog_copy); fGlobalActions.put(ITextEditorActionConstants.COPY, action); action = new TextViewerAction(fPatternEditor, ITextOperationTarget.PASTE); action.setText(PreferencesMessages.EditTemplateDialog_paste); fGlobalActions.put(ITextEditorActionConstants.PASTE, action); action = new TextViewerAction(fPatternEditor, ITextOperationTarget.SELECT_ALL); action.setText(PreferencesMessages.EditTemplateDialog_select_all); fGlobalActions.put(ITextEditorActionConstants.SELECT_ALL, action); action = new TextViewerAction(fPatternEditor, ISourceViewer.CONTENTASSIST_PROPOSALS); action.setText(PreferencesMessages.EditTemplateDialog_content_assist); fGlobalActions.put("ContentAssistProposal", action); fSelectionActions.add(ITextEditorActionConstants.CUT); fSelectionActions.add(ITextEditorActionConstants.COPY); fSelectionActions.add(ITextEditorActionConstants.PASTE); // create context menu MenuManager manager = new MenuManager(null, null); manager.setRemoveAllWhenShown(true); manager.addMenuListener(mgr -> fillContextMenu(mgr)); StyledText text = fPatternEditor.getTextWidget(); Menu menu = manager.createContextMenu(text); text.setMenu(menu); } private void fillContextMenu(IMenuManager menu) { menu.add(new GroupMarker(ITextEditorActionConstants.GROUP_UNDO)); menu.appendToGroup(ITextEditorActionConstants.GROUP_UNDO, fGlobalActions.get(ITextEditorActionConstants.UNDO)); menu.appendToGroup(ITextEditorActionConstants.GROUP_UNDO, fGlobalActions.get(ITextEditorActionConstants.REDO)); menu.add(new Separator(ITextEditorActionConstants.GROUP_EDIT)); menu.appendToGroup(ITextEditorActionConstants.GROUP_EDIT, fGlobalActions.get(ITextEditorActionConstants.CUT)); menu.appendToGroup(ITextEditorActionConstants.GROUP_EDIT, fGlobalActions.get(ITextEditorActionConstants.COPY)); menu.appendToGroup(ITextEditorActionConstants.GROUP_EDIT, fGlobalActions.get(ITextEditorActionConstants.PASTE)); menu.appendToGroup(ITextEditorActionConstants.GROUP_EDIT, fGlobalActions.get(ITextEditorActionConstants.SELECT_ALL)); menu.add(new Separator(IContextMenuConstants.GROUP_GENERATE)); menu.appendToGroup(IContextMenuConstants.GROUP_GENERATE, fGlobalActions.get("ContentAssistProposal")); //$NON-NLS-1$ } protected void updateSelectionDependentActions() { for (String actionId : fSelectionActions) { updateAction(actionId); } } protected void updateAction(String actionId) { IAction action = fGlobalActions.get(actionId); if (action instanceof IUpdate) ((IUpdate) action).update(); } private int getIndex(String contextid) { if (contextid == null) return -1; for (int i = 0; i < fContextTypes.length; i++) { if (contextid.equals(fContextTypes[i][0])) { return i; } } return -1; } @Override protected void okPressed() { String name = fNameText == null ? fTemplate.getName() : fNameText.getText(); boolean isAutoInsertable = fAutoInsertCheckbox != null && fAutoInsertCheckbox.getSelection(); fTemplate = new Template(name, fDescriptionText.getText(), getContextId(), getPattern(), isAutoInsertable); super.okPressed(); } private void updateStatusAndButtons() { StatusInfo status = fValidationStatus; boolean isEmpty = fNameText != null && fNameText.getText().length() == 0; if (!fSuppressError && isEmpty) { status = new StatusInfo(); status.setError( PreferencesMessages.EditTemplateDialog_error_noname); } else if (fNameText != null && !isValidTemplateName(fNameText.getText())) { status = new StatusInfo(); status.setError( PreferencesMessages.EditTemplateDialog_error_invalidName); } updateStatus(status); } /** * Checks whether the given string is a valid template name. * * @param name * the string to test * @return <code>true</code> if the name is valid * @since 3.3.1 */ private boolean isValidTemplateName(String name) { return name.length() == 0 || name.trim().length() != 0; } /* * @see org.eclipse.jface.window.Window#configureShell(Shell) */ @Override protected void configureShell(Shell newShell) { super.configureShell(newShell); // TODO PlatformUI.getWorkbench().getHelpSystem().setHelp(newShell, // IJavaHelpContextIds.EDIT_TEMPLATE_DIALOG); } /** * Returns the created template. * * @return the created template * @since 3.1 */ public Template getTemplate() { return fTemplate; } private String getPattern() { IDocument doc = fPatternEditor.getDocument(); IRegion visible = fPatternEditor.getVisibleRegion(); try { return doc.get(visible.getOffset(), doc.getLength() - visible.getOffset()); } catch (BadLocationException e) { return ""; //$NON-NLS-1$ } } /* * @see org.eclipse.jface.dialogs.Dialog#getDialogBoundsSettings() * * @since 3.2 */ @Override protected IDialogSettings getDialogBoundsSettings() { String sectionName = getClass().getName() + "_dialogBounds"; //$NON-NLS-1$ IDialogSettings settings = DLTKUIPlugin.getDefault() .getDialogSettings(); IDialogSettings section = settings.getSection(sectionName); if (section == null) section = settings.addNewSection(sectionName); return section; } }