blob: aaa90c6d6d344843bf2e0282d578185eee64ea56 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2015, 2019 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.docmlet.base.ui.processing;
import static org.eclipse.statet.jcommons.lang.ObjectUtils.nonNullAssert;
import static org.eclipse.statet.ecommons.databinding.core.observable.ObservableUtils.typed;
import static org.eclipse.statet.docmlet.base.ui.processing.DocProcessingConfig.OUT_FILE_EXT_VAR_NAME;
import static org.eclipse.statet.docmlet.base.ui.processing.DocProcessingConfig.STEP_OUTPUT_FILE_PATH_ATTR_KEY;
import static org.eclipse.statet.docmlet.base.ui.processing.DocProcessingConfig.STEP_OUTPUT_FORMAT_ATTR_KEY;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.UpdateValueStrategy;
import org.eclipse.core.databinding.conversion.IConverter;
import org.eclipse.core.databinding.observable.Realm;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.databinding.observable.value.ValueChangeEvent;
import org.eclipse.core.databinding.observable.value.WritableValue;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.variables.IStringVariable;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.jface.databinding.swt.typed.WidgetProperties;
import org.eclipse.jface.databinding.viewers.typed.ViewerProperties;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.ComboViewer;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.statet.jcommons.collections.ImCollections;
import org.eclipse.statet.jcommons.collections.ImList;
import org.eclipse.statet.jcommons.lang.NonNullByDefault;
import org.eclipse.statet.jcommons.lang.Nullable;
import org.eclipse.statet.ecommons.databinding.core.observable.WritableEqualityValue;
import org.eclipse.statet.ecommons.databinding.core.validation.UpdateableErrorValidator;
import org.eclipse.statet.ecommons.resources.core.variables.ObservableResourcePathVariable;
import org.eclipse.statet.ecommons.resources.core.variables.ResourceVariableResolver;
import org.eclipse.statet.ecommons.resources.core.variables.ResourceVariables;
import org.eclipse.statet.ecommons.ui.util.LayoutUtils;
import org.eclipse.statet.ecommons.ui.util.UIAccess;
import org.eclipse.statet.ecommons.ui.util.VariableFilterUtils;
import org.eclipse.statet.ecommons.ui.workbench.ResourceInputComposite;
import org.eclipse.statet.ecommons.variables.core.ObservableValueVariable;
import org.eclipse.statet.ecommons.variables.core.VariableText2;
import org.eclipse.statet.ecommons.variables.core.VariableUtils;
import org.eclipse.statet.docmlet.base.ui.processing.DocProcessingConfig.CustomExtFormat;
import org.eclipse.statet.docmlet.base.ui.processing.DocProcessingConfig.Format;
import org.eclipse.statet.internal.docmlet.base.ui.processing.Messages;
@NonNullByDefault
public abstract class DocProcessingConfigIOStepTab extends DocProcessingConfigStepTab {
private static final String OUTPUT_PATH_DEFAULT_VALUE=
"${" + ResourceVariables.FILE_NAME_BASE_VAR_NAME + "}" + //$NON-NLS-1$ //$NON-NLS-2$
".${" + DocProcessingConfig.OUT_FILE_EXT_VAR_NAME + "}"; //$NON-NLS-1$ //$NON-NLS-2$
private final String outputFormatAttrName;
private final String outputFilePathAttrName;
private final IObservableValue<@Nullable Format> inputFormatValue;
private final IObservableValue<@Nullable Format> outputFormatValue;
private final IObservableValue<String> outputFileExtValue;
private final IObservableValue<String> outputFilePathValue;
private ImList<Format> availableOutputFormats;
private Format defaultOutputFormat;
private final IObservableValue<@Nullable IFile> resolvedInputFileValue;
private final IObservableValue<@Nullable String> resolvedOutputFileExtValue;
private final IObservableValue<@Nullable IFile> resolvedOutputFileValue;
private final Matcher validExtMatcher= DocProcessingConfig.VALID_EXT_PATTERN.matcher(""); //$NON-NLS-1$
private Label inputControl;
private ComboViewer outputViewer;
private Text outputExtControl;
private ResourceInputComposite outputPathControl;
private IObservableValue<@Nullable IResource> outputPathResourceValue;
public DocProcessingConfigIOStepTab(final DocProcessingConfigMainTab mainTab,
final String stepEnabledAttrName) {
super(mainTab, stepEnabledAttrName);
this.outputFormatAttrName= getAttrQualifier() + '/' + STEP_OUTPUT_FORMAT_ATTR_KEY;
this.outputFilePathAttrName= getAttrQualifier() + '/' + STEP_OUTPUT_FILE_PATH_ATTR_KEY;
final Realm realm= getRealm();
this.inputFormatValue= new WritableValue<>(realm, null, Format.class);
this.inputFormatValue.addValueChangeListener(this);
this.outputFormatValue= new WritableValue<>(realm, null, Format.class);
this.outputFormatValue.addValueChangeListener(this);
this.outputFileExtValue= new WritableValue<>(realm, "", String.class); //$NON-NLS-1$
this.outputFileExtValue.addValueChangeListener(this);
this.outputFilePathValue= new WritableValue<>(realm, "", String.class); //$NON-NLS-1$
this.resolvedInputFileValue= new WritableEqualityValue<>(realm, null, IFile.class);
this.resolvedInputFileValue.addValueChangeListener(this);
this.resolvedOutputFileExtValue= new WritableEqualityValue<>(realm, null, String.class);
this.resolvedOutputFileValue= new WritableEqualityValue<>(realm, null, IFile.class);
}
@Override
protected void initVariables(final Map<String, IStringVariable> variables) {
variables.putAll(getMainTab().getSourceFileVariables());
VariableUtils.add(variables, new ObservableResourcePathVariable<>( // required for updates
DocProcessingConfig.IN_FILE_PATH_VAR,
this.resolvedInputFileValue ));
VariableUtils.add(variables, INPUT_RESOURCE_VAR_DEFS,
new ResourceVariableResolver(new ResourceVariableResolver.Context() {
@Override
public @Nullable IResource getResource() {
return DocProcessingConfigIOStepTab.this.resolvedInputFileValue.getValue();
}
}, ResourceVariableResolver.EXISTS_NEVER ));
VariableUtils.add(variables, new ObservableValueVariable<>(
DocProcessingConfig.OUT_FILE_EXT_VAR,
this.resolvedOutputFileExtValue ));
VariableUtils.add(variables, new ObservableResourcePathVariable<>(
DocProcessingConfig.OUT_FILE_PATH_VAR,
this.resolvedOutputFileValue ));
}
protected void setInput(final @Nullable Format format, final @Nullable IFile file) {
if (format != null && format.equals(this.inputFormatValue.getValue())) {
this.resolvedInputFileValue.setValue(file);
}
else if (file == null || !file.equals(this.resolvedInputFileValue.getValue())) {
this.resolvedInputFileValue.setValue(null);
this.inputFormatValue.setValue(format);
this.resolvedInputFileValue.setValue(file);
}
else {
this.inputFormatValue.setValue(format);
}
}
protected void setAvailableOutputFormats(final List<Format> formats, final String defaultKey) {
nonNullAssert(formats);
nonNullAssert(defaultKey);
final Format defaultFormat= DocProcessingConfig.getFormat(formats, null, defaultKey);
if (defaultFormat == null) {
throw new IllegalArgumentException("defaultKey: default format not found"); //$NON-NLS-1$
}
this.availableOutputFormats= ImCollections.toList(formats);
this.defaultOutputFormat= defaultFormat;
if (this.outputViewer != null) {
this.outputViewer.setInput(formats);
}
}
protected @Nullable Format getOutputFormat(final String key, final boolean fallbackDefault) {
return DocProcessingConfig.getFormat(this.availableOutputFormats,
(fallbackDefault) ? this.defaultOutputFormat : null,
key );
}
protected void setOutputFormat(final Format format) {
this.outputFormatValue.setValue(format);
}
public @Nullable Format getInputFormat() {
return this.inputFormatValue.getValue();
}
public @Nullable IFile getInputFile() {
return this.resolvedInputFileValue.getValue();
}
public IObservableValue<@Nullable IFile> getInputFileValue() {
return this.resolvedInputFileValue;
}
public @Nullable Format getOutputFormat() {
return this.outputFormatValue.getValue();
}
public IObservableValue<@Nullable String> getOutputFileExtValue() {
return this.resolvedOutputFileExtValue;
}
public @Nullable IFile getOutputFile() {
return this.resolvedOutputFileValue.getValue();
}
public IObservableValue<@Nullable IFile> getOutputFileValue() {
return this.resolvedOutputFileValue;
}
@Override
public String getInfo() {
final StringBuilder sb= getStringBuilder();
final Format inputFormat= getInputFormat();
final Format outputFormat= getOutputFormat();
sb.append((inputFormat != null) ? inputFormat.getInfoLabel() : "?"); //$NON-NLS-1$
sb.append("\u2002\u2192\u2002"); //$NON-NLS-1$
sb.append((outputFormat != null) ? outputFormat.getInfoLabel() : "?"); //$NON-NLS-1$
sb.append('\n');
final DocProcessingOperationSettings operation= getOperation();
sb.append((operation != null) ? operation.getInfo() : " "); //$NON-NLS-1$
return sb.toString();
}
@Override
public void handleValueChange(final ValueChangeEvent<?> event) {
if (event.getObservable() == this.inputFormatValue) {
updateInputText();
resolveInputFile();
}
if (event.getObservable() == this.outputFormatValue) {
final Format format= typed(event, this.outputFormatValue).diff.getNewValue();
this.outputExtControl.setEditable(format instanceof CustomExtFormat);
this.outputFileExtValue.setValue((format != null) ? format.getExt() : ""); //$NON-NLS-1$
resolveOutputFileExt();
}
if (event.getObservable() == this.outputFileExtValue) {
Format format= this.outputFormatValue.getValue();
if (format instanceof CustomExtFormat) {
final String ext= typed(event, this.outputFileExtValue).diff.getNewValue();
if (!ext.equals(format.getExt())) {
format= new CustomExtFormat((CustomExtFormat) format, ext);
this.outputFormatValue.setValue(format);
}
}
return;
}
if (event.getObservable() == this.resolvedInputFileValue) {
resolveOutputFileExt();
return;
}
if (event.getObservable() == this.outputPathResourceValue) {
final IResource resource= typed(event, this.outputPathResourceValue).diff.getNewValue();
this.resolvedOutputFileValue.setValue((resource instanceof IFile) ? (IFile) resource : null);
return;
}
super.handleValueChange(event);
}
protected void resolveInputFile() {
IFile file= null;
try {
final Format format= getInputFormat();
if (format != null) {
if (format.matches(DocProcessingConfig.SOURCE_FORMAT_KEY)) {
file= getMainTab().getSourceFile();
}
else {
file= getInputFile(format);
}
}
}
catch (final Exception e) {}
this.resolvedInputFileValue.setValue(file);
}
protected @Nullable IFile getInputFile(final Format format) throws CoreException {
return null;
}
protected void resolveOutputFileExt() {
String ext= null;
try {
final Format format= getOutputFormat();
if (format != null) {
final IFile inputFile= getInputFile();
ext= format.getExt(getValidExt((inputFile != null) ? inputFile.getName() : null));
}
if (ext != null && !this.validExtMatcher.reset(ext).matches()) {
ext= null;
}
}
catch (final Exception e) {}
this.resolvedOutputFileExtValue.setValue(ext);
}
private @Nullable String getValidExt(final @Nullable String name) {
if (name != null) {
final int idx= name.lastIndexOf('.');
if (idx >= 0) {
final String ext= name.substring(idx + 1);
if (this.validExtMatcher.reset(ext).matches()) {
return ext;
}
}
}
return null;
}
@Override
protected void addControls(final Composite parent) {
{ final Composite group= createIOGroup(parent);
group.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
}
{ final Composite group= createOperationGroup(parent);
group.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
}
}
protected Composite createIOGroup(final Composite parent) {
final Group group= new Group(parent, SWT.NONE);
group.setLayout(LayoutUtils.newGroupGrid(4));
group.setText("IO"); //$NON-NLS-1$
{ final Label label= new Label(group, SWT.NONE);
label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
label.setText(Messages.StepTab_In_label);
}
{ final Label label= new Label(group, SWT.NONE);
label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 3, 1));
this.inputControl= label;
updateInputText();
}
{ final Label label= new Label(group, SWT.NONE);
label.setText(Messages.StepTab_Out_label);
label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
}
{ { final Label label= new Label(group, SWT.NONE);
label.setText(Messages.StepTab_Out_Format_label + ':');
label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
}
final Composite composite= new Composite(group, SWT.NONE);
composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 2, 1));
composite.setLayout(LayoutUtils.newCompositeGrid(3));
{ final ComboViewer viewer= new ComboViewer(composite, SWT.DROP_DOWN | SWT.READ_ONLY | SWT.BORDER);
final Combo combo= viewer.getCombo();
final LabelProvider labelProvider= new LabelProvider();
viewer.setContentProvider(new ArrayContentProvider());
viewer.setLabelProvider(labelProvider);
viewer.setInput(this.availableOutputFormats);
final GridData gd= new GridData(SWT.FILL, SWT.CENTER, false, false);
gd.widthHint= LayoutUtils.hintWidth(combo, this.availableOutputFormats, labelProvider);
combo.setLayoutData(gd);
this.outputViewer= viewer;
}
{ final Label label= new Label(composite, SWT.NONE);
label.setText(Messages.StepTab_Out_FileExt_label + " (" + OUT_FILE_EXT_VAR_NAME + "):"); //$NON-NLS-1$ //$NON-NLS-2$
label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
}
{ final Text text= new Text(composite, SWT.BORDER);
text.setEditable(false);
final GridData gd= new GridData(SWT.LEFT, SWT.CENTER, true, false);
gd.widthHint= LayoutUtils.hintWidth(text, 25);
text.setLayoutData(gd);
this.outputExtControl= text;
}
}
LayoutUtils.addGDDummy(group); // To
{ final Label label= new Label(group, SWT.NONE);
label.setText(Messages.StepTab_Out_FilePath_label + ':');
label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
}
{ final ResourceInputComposite input= new ResourceInputComposite(group,
ResourceInputComposite.STYLE_TEXT,
ResourceInputComposite.MODE_FILE | ResourceInputComposite.MODE_WS_ONLY,
Messages.StepTab_Out_FilePath_label );
input.setShowInsertVariable(true, VariableFilterUtils.DEFAULT_INTERACTIVE_RESOURCE_FILTERS, null);
input.getValidator().setOnExisting(IStatus.OK);
input.getValidator().setOnLateResolve(IStatus.OK);
input.getValidator().setRelative(getMainTab().getWorkingDirectoryPathVariable(), -1);
input.getValidator().setIgnoreRelative(true);
final Map<String, IStringVariable> variables= new HashMap<>();
variables.putAll(getStepVariables());
variables.remove(DocProcessingConfig.OUT_FILE_PATH_VAR_NAME);
input.getValidator().setVariableResolver(new VariableText2(variables));
input.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
this.outputPathControl= input;
this.outputPathResourceValue= input.getValidator().getWorkspaceResourceObservable();
this.outputPathResourceValue.addValueChangeListener(this);
}
{ final Label label= new Label(group, SWT.NONE);
label.setText("(relative to working directory)");
label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
}
return group;
}
protected void updateInputText() {
final Format inputFormat= getInputFormat();
if (!UIAccess.isOkToUse(this.inputControl) || inputFormat == null) {
return;
}
this.inputControl.setText(inputFormat.getInfoLabel());
}
@Override
protected void addBindings(final DataBindingContext dbc) {
super.addBindings(dbc);
final IObservableValue<@Nullable Format> outputFormatObservable=
ViewerProperties.singleSelection(Format.class)
.observe(this.outputViewer);
dbc.bindValue(
outputFormatObservable,
this.outputFormatValue,
null,
new UpdateValueStrategy<@Nullable Format, @Nullable Format>()
.setConverter(new IConverter<@Nullable Format, @Nullable Format>() {
@Override
public Object getToType() {
return Format.class;
}
@Override
public Object getFromType() {
return Format.class;
}
@Override
public @Nullable Format convert(final @Nullable Format fromObject) {
if (fromObject != null) {
return DocProcessingConfig.getFormat(
DocProcessingConfigIOStepTab.this.availableOutputFormats,
fromObject.getKey() );
}
return null;
}
}));
dbc.bindValue(
WidgetProperties.text(SWT.Modify)
.observe(this.outputExtControl),
this.outputFileExtValue,
new UpdateValueStrategy<>(), //?
null );
dbc.bindValue(
this.outputPathControl.getObservable(),
this.outputFilePathValue,
new UpdateValueStrategy<String, String>()
.setAfterGetValidator(new UpdateableErrorValidator<>(
this.outputPathControl.getValidator() )),
null );
}
@Override
public void setDefaults(final ILaunchConfigurationWorkingCopy configuration) {
super.setDefaults(configuration);
configuration.setAttribute(this.outputFormatAttrName, this.defaultOutputFormat.getKey());
configuration.setAttribute(this.outputFilePathAttrName, OUTPUT_PATH_DEFAULT_VALUE);
}
@Override
protected void doInitialize(final ILaunchConfiguration configuration) {
super.doInitialize(configuration);
{ final String key= readAttribute(configuration,
this.outputFormatAttrName,
this.defaultOutputFormat.getKey() );
final Format format= getOutputFormat(key, true);
this.outputFormatValue.setValue(format);
}
{ final String path= readAttribute(configuration,
this.outputFilePathAttrName,
OUTPUT_PATH_DEFAULT_VALUE );
this.outputFilePathValue.setValue(path);
}
}
@Override
protected void doSave(final ILaunchConfigurationWorkingCopy configuration) {
super.doSave(configuration);
{ final Format format= getOutputFormat();
configuration.setAttribute(this.outputFormatAttrName, format.getKey());
}
{ final String path= this.outputFilePathValue.getValue();
configuration.setAttribute(this.outputFilePathAttrName, path);
}
}
}