blob: 15e895964c745a3c7e79c8dd8f14867321e940e0 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2005, 2021 Stephan Wahlbrink and others.
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
# which is available at https://www.apache.org/licenses/LICENSE-2.0.
#
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
#
# Contributors:
# Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
#=============================================================================*/
package org.eclipse.statet.ltk.ui.templates.config;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.eclipse.core.databinding.validation.ValidationStatus;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextListener;
import org.eclipse.jface.text.TextEvent;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.source.SourceViewer;
import org.eclipse.jface.text.templates.Template;
import org.eclipse.jface.text.templates.TemplateContextType;
import org.eclipse.jface.text.templates.TemplateException;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.ComboViewer;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.text.templates.ContextTypeRegistry;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds;
import org.eclipse.statet.ecommons.templates.TemplateVariableProcessor;
import org.eclipse.statet.ecommons.text.ui.TextViewerAction;
import org.eclipse.statet.ecommons.ui.components.StatusInfo;
import org.eclipse.statet.ecommons.ui.dialogs.DialogUtils;
import org.eclipse.statet.ecommons.ui.dialogs.ExtStatusDialog;
import org.eclipse.statet.ecommons.ui.util.LayoutUtils;
import org.eclipse.statet.ecommons.ui.viewers.ViewerUtils;
import org.eclipse.statet.internal.ltk.ui.EditingMessages;
import org.eclipse.statet.internal.ltk.ui.LtkUIPlugin;
import org.eclipse.statet.ltk.ui.sourceediting.SnippetEditor;
import org.eclipse.statet.ltk.ui.sourceediting.SnippetEditor1;
import org.eclipse.statet.ltk.ui.sourceediting.SourceEditorViewerConfigurator;
/**
* Dialog to edit a template.
*/
public class EditTemplateDialog extends ExtStatusDialog {
public static final int EDITOR_TEMPLATE= 1;
public static final int CUSTOM_TEMPLATE= 2;
public static final int FIX_TEMPLATE= 3;
private final Template originalTemplate;
private Template newTemplate;
private final int flags;
private final SourceEditorViewerConfigurator configurator;
private Text nameText;
private Text descriptionText;
private ComboViewer contextCombo;
private final SnippetEditor1 patternEditor;
private Button insertVariableButton;
private Button autoInsertCheckbox;
private IStatus validationStatus;
private boolean suppressError= true;
private final ContextTypeRegistry contextTypeRegistry;
private final TemplateVariableProcessor templateProcessor;
/**
* 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 flags edit mode
* @param configurator configurator for the source viewer
* @param processor the template variable processor
* @param registry the context type registry to use
*/
public EditTemplateDialog(final Shell parent, final Template template,
final boolean edit, final int flags,
final SourceEditorViewerConfigurator configurator,
final TemplateVariableProcessor processor, final ContextTypeRegistry registry,
final String prefQualifier) {
super(parent);
setTitle(edit ?
EditingMessages.EditTemplateDialog_title_Edit :
EditingMessages.EditTemplateDialog_title_New );
this.originalTemplate= template;
this.flags= flags;
this.templateProcessor= processor;
this.contextTypeRegistry= registry;
final TemplateContextType type= this.contextTypeRegistry.getContextType(template.getContextTypeId());
this.templateProcessor.setContextType(type);
this.configurator= configurator;
this.patternEditor= new SnippetEditor1(this.configurator, template.getPattern(),
PlatformUI.getWorkbench(), prefQualifier );
}
/**
* Returns the created template.
*
* @return the created template
* @since 3.1
*/
public Template getTemplate() {
return this.newTemplate;
}
@Override
public void create() {
super.create();
// update initial OK button to be disabled for new templates
final boolean valid= this.nameText == null || this.nameText.getText().trim().length() != 0;
if (!valid) {
final StatusInfo status= new StatusInfo();
status.setError(EditingMessages.EditTemplateDialog_error_NoName);
updateButtonsEnableState(status);
}
}
@Override
protected Control createDialogArea(final Composite parent) {
final Composite dialogArea= new Composite(parent, SWT.NONE);
dialogArea.setLayout(LayoutUtils.newDialogGrid(2));
dialogArea.setLayoutData(new GridData(GridData.FILL_BOTH));
final ModifyListener listener= new ModifyListener() {
@Override
public void modifyText(final ModifyEvent e) {
EditTemplateDialog.this.suppressError= false;
updateButtons();
}
};
if ((this.flags & 0xf) == EDITOR_TEMPLATE) {
createLabel(dialogArea, EditingMessages.EditTemplateDialog_Name_label);
final Composite composite= new Composite(dialogArea, SWT.NONE);
composite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
composite.setLayout(LayoutUtils.newCompositeGrid(4));
this.nameText= createText(composite);
this.nameText.addModifyListener(listener);
this.nameText.addFocusListener(new FocusListener() {
@Override
public void focusGained(final FocusEvent e) {
}
@Override
public void focusLost(final FocusEvent e) {
if (EditTemplateDialog.this.suppressError) {
EditTemplateDialog.this.suppressError= false;
updateButtons();
}
}
});
createLabel(composite, EditingMessages.EditTemplateDialog_Context_label);
this.contextCombo= new ComboViewer(composite, SWT.BORDER | SWT.READ_ONLY);
this.contextCombo.setLabelProvider(new LabelProvider() {
@Override
public String getText(final Object element) {
return ((TemplateContextType) element).getName();
}
});
this.contextCombo.setContentProvider(new ArrayContentProvider());
final List<TemplateContextType> contextTypes= new ArrayList<>();
for (final Iterator<TemplateContextType> iter= this.contextTypeRegistry.contextTypes(); iter.hasNext(); ) {
contextTypes.add(iter.next());
}
this.contextCombo.setInput(contextTypes.toArray());
this.contextCombo.addSelectionChangedListener(new ISelectionChangedListener() {
@Override
public void selectionChanged(final SelectionChangedEvent event) {
final StructuredSelection selection= (StructuredSelection) event.getSelection();
doContextChanged(((TemplateContextType) selection.getFirstElement()));
}
});
ViewerUtils.setDefaultVisibleItemCount(this.contextCombo);
this.autoInsertCheckbox= createCheckbox(composite, EditingMessages.EditTemplateDialog_AutoInsert_label);
this.autoInsertCheckbox.setSelection(this.originalTemplate.isAutoInsertable());
}
else {
configureForContext(getContextType());
}
createLabel(dialogArea, EditingMessages.EditTemplateDialog_Description_label);
final int descFlags= ((this.flags & 0xf) == FIX_TEMPLATE) ? (SWT.BORDER | SWT.READ_ONLY) : SWT.BORDER;
this.descriptionText= new Text(dialogArea, descFlags);
this.descriptionText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
this.descriptionText.addModifyListener(listener);
final Label patternLabel= createLabel(dialogArea, EditingMessages.EditTemplateDialog_Pattern_label);
patternLabel.setLayoutData(new GridData(GridData.VERTICAL_ALIGN_BEGINNING));
createEditor(dialogArea);
final Label filler= new Label(dialogArea, SWT.NONE);
filler.setLayoutData(new GridData());
final Composite composite= new Composite(dialogArea, SWT.NONE);
composite.setLayout(LayoutUtils.newCompositeGrid(1));
composite.setLayoutData(new GridData());
this.insertVariableButton= new Button(composite, SWT.NONE);
this.insertVariableButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
this.insertVariableButton.setText(EditingMessages.EditTemplateDialog_InsertVariable);
this.insertVariableButton.addSelectionListener(new SelectionListener() {
@Override
public void widgetSelected(final SelectionEvent e) {
insertVariablePressed();
}
@Override
public void widgetDefaultSelected(final SelectionEvent e) {}
});
this.descriptionText.setText(this.originalTemplate.getDescription());
if (this.nameText != null) {
this.nameText.setText(this.originalTemplate.getName());
this.nameText.addModifyListener(listener);
this.contextCombo.setSelection(new StructuredSelection(this.contextTypeRegistry.getContextType(this.originalTemplate.getContextTypeId())));
}
else {
this.patternEditor.getControl().setFocus();
}
final TextViewerAction assistAction= new TextViewerAction(this.patternEditor.getSourceViewer(), ISourceViewer.CONTENTASSIST_PROPOSALS);
assistAction.setId("ContentAssistProposal"); //$NON-NLS-1$
assistAction.setText(EditingMessages.EditTemplateDialog_ContentAssist);
assistAction.setActionDefinitionId(ITextEditorActionDefinitionIds.CONTENT_ASSIST_PROPOSALS);
this.patternEditor.addAction(assistAction);
LayoutUtils.addSmallFiller(dialogArea, false);
applyDialogFont(dialogArea);
return composite;
}
protected SourceViewer getSourceViewer() {
return this.patternEditor.getSourceViewer();
}
protected SourceEditorViewerConfigurator getSourceViewerConfigurator() {
return this.configurator;
}
/* GUI Methods ****************************************************************/
private static Label createLabel(final Composite parent, final String name) {
final Label label= new Label(parent, SWT.NULL);
label.setText(name);
label.setLayoutData(new GridData());
return label;
}
private static Text createText(final Composite parent) {
final Text text= new Text(parent, SWT.BORDER);
text.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
return text;
}
private static Button createCheckbox(final Composite parent, final String name) {
final Button button= new Button(parent, SWT.CHECK);
button.setText(name);
button.setLayoutData(new GridData());
return button;
}
private void createEditor(final Composite parent) {
int nLines= this.patternEditor.getDocument().getNumberOfLines();
if (nLines < 6) {
nLines= 6;
} else if (nLines > 12) {
nLines= 12;
}
this.patternEditor.create(parent, SnippetEditor.DEFAULT_MULTI_LINE_STYLE);
final Control control= this.patternEditor.getControl();
final GridData data= new GridData(GridData.FILL_BOTH);
data.widthHint= convertWidthInCharsToPixels(80);
data.heightHint= convertHeightInCharsToPixels(nLines);
control.setLayoutData(data);
this.patternEditor.getSourceViewer().addTextListener(new ITextListener() {
@Override
public void textChanged(final TextEvent event) {
if (event.getDocumentEvent() != null) {
doSourceChanged(event.getDocumentEvent().getDocument());
}
}
});
}
/* Handlers *******************************************************************/
private void doContextChanged(final TemplateContextType contextType) {
this.templateProcessor.setContextType(contextType);
configureForContext(contextType);
final Document document= this.patternEditor.getDocument();
doValidate(contextType, document);
updateButtons();
}
private void doSourceChanged(final IDocument document) {
final TemplateContextType contextType= getContextType();
doValidate(contextType, document);
updateButtons();
}
private void doValidate(final TemplateContextType contextType, final IDocument document) {
final String text= document.get();
this.validationStatus= null;
if (contextType != null) {
final IStatus status= validate(contextType, text);
if (status != null && !status.isOK()) {
this.validationStatus= status;
}
}
}
protected IStatus validate(final TemplateContextType contextType, final String text) {
try {
contextType.validate(text);
return ValidationStatus.ok();
}
catch (final TemplateException e) {
return ValidationStatus.error(e.getLocalizedMessage());
}
}
protected void configureForContext(final TemplateContextType contextType) {
}
protected void insertVariablePressed() {
this.patternEditor.getSourceViewer().getTextWidget().setFocus();
this.patternEditor.getSourceViewer().doOperation(ISourceViewer.CONTENTASSIST_PROPOSALS);
}
protected void insertText(final String text) {
this.patternEditor.getSourceViewer().getTextWidget().insert(text);
}
@Override
protected void okPressed() {
final String name= this.nameText == null ? this.originalTemplate.getName() : this.nameText.getText();
final boolean isAutoInsertable= this.autoInsertCheckbox != null && this.autoInsertCheckbox.getSelection();
this.newTemplate= new Template(name, this.descriptionText.getText(), getContextType().getId(), this.patternEditor.getDocument().get(), isAutoInsertable);
super.okPressed();
}
private void updateButtons() {
IStatus status;
final boolean valid= (this.nameText == null || this.nameText.getText().trim().length() != 0);
if (!valid) {
final StatusInfo info= new StatusInfo();
if (!this.suppressError) {
info.setError(EditingMessages.EditTemplateDialog_error_NoName);
}
status= info;
} else if (!isValidPattern(this.patternEditor.getDocument().get())) {
final StatusInfo info= new StatusInfo();
if (!this.suppressError) {
info.setError(EditingMessages.EditTemplateDialog_error_invalidPattern);
}
status= info;
} else {
status= (this.validationStatus != null) ? this.validationStatus : ValidationStatus.ok();
}
updateStatus(status);
}
/**
* Validates the pattern.
* <p>
* The default implementation rejects invalid XML characters.
* </p>
*
* @param pattern the pattern to verify
* @return <code>true</code> if the pattern is valid
*/
protected boolean isValidPattern(final String pattern) {
for (int i= 0; i < pattern.length(); i++) {
final char ch= pattern.charAt(i);
if (!(ch == 9 || ch == 10 || ch == 13 || ch >= 32)) {
return false;
}
}
return true;
}
/* ******/
protected TemplateContextType getContextType() {
if (this.contextCombo != null) {
final StructuredSelection selection= (StructuredSelection) this.contextCombo.getSelection();
return ((TemplateContextType) selection.getFirstElement());
}
else {
return this.contextTypeRegistry.getContextType(this.originalTemplate.getContextTypeId());
}
}
protected IDialogSettings getDialogSettings() {
return DialogUtils.getDialogSettings(LtkUIPlugin.getInstance(), "TemplateEditDialog"); //$NON-NLS-1$
}
@Override
protected IDialogSettings getDialogBoundsSettings() {
return getDialogSettings();
}
}