/* * Copyright (c) 2015 the original author or authors. * 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: * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation */ package org.eclipse.buildship.ui.launch; import java.util.List; import org.gradle.util.GradleVersion; import com.google.common.base.Function; import com.google.common.base.Optional; import com.google.common.base.Strings; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; import com.gradleware.tooling.toolingutils.binding.Validator; import org.eclipse.debug.core.ILaunchConfiguration; import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; import org.eclipse.debug.ui.AbstractLaunchConfigurationTab; import org.eclipse.swt.SWT; import org.eclipse.swt.events.ModifyEvent; import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.Image; 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.Group; import org.eclipse.swt.widgets.Text; import org.eclipse.buildship.core.CorePlugin; import org.eclipse.buildship.core.GradlePluginsRuntimeException; import org.eclipse.buildship.core.i18n.CoreMessages; import org.eclipse.buildship.core.launch.GradleRunConfigurationAttributes; import org.eclipse.buildship.core.util.gradle.GradleDistributionValidator; import org.eclipse.buildship.core.util.gradle.GradleDistributionWrapper; import org.eclipse.buildship.core.util.gradle.GradleDistributionWrapper.DistributionType; import org.eclipse.buildship.core.util.gradle.PublishedGradleVersionsWrapper; import org.eclipse.buildship.ui.PluginImage.ImageState; import org.eclipse.buildship.ui.PluginImages; import org.eclipse.buildship.ui.i18n.UiMessages; import org.eclipse.buildship.ui.util.file.DirectoryDialogSelectionListener; import org.eclipse.buildship.ui.util.font.FontUtils; import org.eclipse.buildship.ui.util.selection.Enabler; import org.eclipse.buildship.ui.util.widget.ButtonUtils; import org.eclipse.buildship.ui.util.widget.UiBuilder; /** * Specifies the Gradle distribution to apply when executing tasks via the run configurations. */ public final class ProjectSettingsTab extends AbstractLaunchConfigurationTab { private final Font defaultFont; private final UiBuilder.UiBuilderFactory builderFactory; private final Validator<GradleDistributionWrapper> gradleDistributionValidator; private final PublishedGradleVersionsWrapper publishedGradleVersions; private Text localInstallationDirText; private Text remoteDistributionUriText; private Combo gradleVersionCombo; private Button useGradleWrapperOption; private Button useLocalInstallationDirOption; private Button localInstallationDirBrowseButton; private Button useRemoteDistributionUriOption; private Button useGradleVersionOption; private List<Button> allRadioButtons; private Button overrideBuildSettingsCheckbox; private Button offlineModeCheckbox; private Button buildScansEnabledCheckbox; private Enabler overrideProjectSettingsEnabler; private final SelectionListener optionSelectionChangedListener; private final ModifyListener textChangedListener; // if different configurations are loaded we change radio buttons and text fields. this // generates several change events which trigger the launch config to update and leads to // an inconsistent state. to resolve that, we disable the launch config update in when the // initializeFrom() method is called private boolean disableUpdateDialog = false; public ProjectSettingsTab() { this.defaultFont = FontUtils.getDefaultDialogFont(); this.builderFactory = new UiBuilder.UiBuilderFactory(this.defaultFont); this.gradleDistributionValidator = GradleDistributionValidator.gradleDistributionValidator(); this.publishedGradleVersions = CorePlugin.publishedGradleVersions(); this.optionSelectionChangedListener = new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { if (!ProjectSettingsTab.this.disableUpdateDialog) { updateLaunchConfigurationDialog(); } } }; this.textChangedListener = new ModifyListener() { @Override public void modifyText(ModifyEvent e) { if (!ProjectSettingsTab.this.disableUpdateDialog) { updateLaunchConfigurationDialog(); } } }; } @Override public String getName() { return LaunchMessages.Tab_Name_ProjectSettings; } @Override public Image getImage() { return PluginImages.RUN_CONFIG_GRADLE_DISTRIBUTION.withState(ImageState.ENABLED).getImage(); } @Override public void createControl(Composite parent) { Composite page = new Composite(parent, SWT.NONE); GridLayout layout = new GridLayout(1, false); page.setLayout(layout); setControl(page); this.overrideBuildSettingsCheckbox = new Button(page, SWT.CHECK); this.overrideBuildSettingsCheckbox.setText(CoreMessages.RunConfiguration_Label_OverrideProjectSettings); Group overridePreferencesGroup = createGroup(page, CoreMessages.RunConfiguration_Label_BuildExecution + ":"); createOverrideProjectPreferencesControl(overridePreferencesGroup); Group gradleDistributionGroup = createGroup(page, CoreMessages.RunConfiguration_Label_GradleDistribution + ":"); createGradleDistributionSelectionControl(gradleDistributionGroup); this.overrideProjectSettingsEnabler = new Enabler(this.overrideBuildSettingsCheckbox); this.overrideProjectSettingsEnabler.enables(this.offlineModeCheckbox, this.buildScansEnabledCheckbox, this.useGradleWrapperOption, this.useLocalInstallationDirOption, this.localInstallationDirText, this.localInstallationDirBrowseButton, this.useRemoteDistributionUriOption, this.remoteDistributionUriText, this.useGradleVersionOption, this.gradleVersionCombo); } private Group createGroup(Composite parent, String groupName) { Group group = new Group(parent, SWT.NONE); group.setText(groupName); group.setLayout(new GridLayout(3, false)); group.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); return group; } private void createGradleDistributionSelectionControl(Group container) { // gradle wrapper this.useGradleWrapperOption = this.builderFactory.newRadio(container).alignLeft().text(CoreMessages.GradleDistribution_Label_GradleWrapper).control(); this.builderFactory.span(container); this.builderFactory.span(container); // local installation directory this.useLocalInstallationDirOption = this.builderFactory.newRadio(container).alignLeft().text(CoreMessages.GradleDistribution_Label_LocalInstallationDirectory).control(); this.localInstallationDirText = this.builderFactory.newText(container).alignFillHorizontal().disabled().control(); this.localInstallationDirBrowseButton = this.builderFactory.newButton(container).alignLeft().disabled().text(UiMessages.Button_Label_Browse).control(); this.localInstallationDirBrowseButton.addSelectionListener(new DirectoryDialogSelectionListener(container.getShell(), this.localInstallationDirText, CoreMessages.GradleDistribution_Label_LocalInstallationDirectory)); // remote distribution installation this.useRemoteDistributionUriOption = this.builderFactory.newRadio(container).alignLeft().text(CoreMessages.GradleDistribution_Label_RemoteDistributionUri).control(); this.remoteDistributionUriText = this.builderFactory.newText(container).alignFillHorizontal().disabled().control(); this.builderFactory.span(container); // gradle version this.useGradleVersionOption = this.builderFactory.newRadio(container).alignLeft().text(CoreMessages.GradleDistribution_Label_SpecificGradleVersion).control(); this.gradleVersionCombo = this.builderFactory.newCombo(container).alignLeft().disabled().control(); this.gradleVersionCombo.setSize(150, this.gradleVersionCombo.getSize().y); this.gradleVersionCombo.setItems(getGradleVersions()); this.builderFactory.span(container); // update launch configuration when the content of the widgets are changing this.localInstallationDirText.addModifyListener(this.textChangedListener); this.remoteDistributionUriText.addModifyListener(this.textChangedListener); this.gradleVersionCombo.addModifyListener(this.textChangedListener); // update the enabled/disabled state when the currently selected radio is changing this.useGradleWrapperOption.addSelectionListener(this.optionSelectionChangedListener); this.useLocalInstallationDirOption.addSelectionListener(this.optionSelectionChangedListener); this.useRemoteDistributionUriOption.addSelectionListener(this.optionSelectionChangedListener); this.useGradleVersionOption.addSelectionListener(this.optionSelectionChangedListener); // update the enabled/disabled state when the currently selected radio is changing new Enabler(this.useGradleWrapperOption).enables(); new Enabler(this.useLocalInstallationDirOption).enables(this.localInstallationDirText, this.localInstallationDirBrowseButton); new Enabler(this.useRemoteDistributionUriOption).enables(this.remoteDistributionUriText); new Enabler(this.useGradleVersionOption).enables(this.gradleVersionCombo); // save all created radio buttons in a list this.allRadioButtons = ImmutableList.of(this.useGradleWrapperOption, this.useLocalInstallationDirOption, this.useRemoteDistributionUriOption, this.useGradleVersionOption); } private void createOverrideProjectPreferencesControl(Composite container) { GridLayout layout = new GridLayout(3, false); container.setLayout(layout); this.offlineModeCheckbox = new Button(container, SWT.CHECK); this.offlineModeCheckbox.setText("Offline Mode"); this.buildScansEnabledCheckbox = new Button(container, SWT.CHECK); this.buildScansEnabledCheckbox.setText("Build Scans"); this.overrideBuildSettingsCheckbox.addSelectionListener(new DialogUpdater()); this.offlineModeCheckbox.addSelectionListener(new DialogUpdater()); this.buildScansEnabledCheckbox.addSelectionListener(new DialogUpdater()); } private String[] getGradleVersions() { return FluentIterable.from(this.publishedGradleVersions.getVersions()).transform(new Function<GradleVersion, String>() { @Override public String apply(GradleVersion gradleVersion) { return gradleVersion.getVersion(); } }).toArray(String.class); } @Override public void initializeFrom(ILaunchConfiguration configuration) { // do not trigger save configuration (through events) when initializing the values this.disableUpdateDialog = true; try { GradleRunConfigurationAttributes configurationAttributes = GradleRunConfigurationAttributes.from(configuration); setSelection(GradleDistributionWrapper.from(configurationAttributes.getGradleDistribution())); this.overrideBuildSettingsCheckbox.setSelection(configurationAttributes.isOverrideBuildSettings()); this.offlineModeCheckbox.setSelection(configurationAttributes.isOffline()); this.buildScansEnabledCheckbox.setSelection(configurationAttributes.isBuildScansEnabled()); this.overrideProjectSettingsEnabler.updateEnablement(); } finally { this.disableUpdateDialog = false; } } private void resetValues() { this.localInstallationDirText.setText(""); this.remoteDistributionUriText.setText(""); if (this.gradleVersionCombo.getItemCount() > 0) { this.gradleVersionCombo.select(0); } else { this.gradleVersionCombo.setText(""); } } private void setSelection(GradleDistributionWrapper wrapper) { resetValues(); // set correct enabled state and specific configuration values DistributionType type = wrapper.getType(); String configuration = Strings.nullToEmpty(wrapper.getConfiguration()); switch (type) { case WRAPPER: updateSelectedState(this.useGradleWrapperOption, this.allRadioButtons); break; case LOCAL_INSTALLATION: updateSelectedState(this.useLocalInstallationDirOption, this.allRadioButtons); this.localInstallationDirText.setText(configuration); break; case REMOTE_DISTRIBUTION: updateSelectedState(this.useRemoteDistributionUriOption, this.allRadioButtons); this.remoteDistributionUriText.setText(configuration); break; case VERSION: updateSelectedState(this.useGradleVersionOption, this.allRadioButtons); updateVersionSelection(configuration); break; default: throw new GradlePluginsRuntimeException("Unrecognized Gradle distribution type: " + type); //$NON-NLS-1$ } } private void updateSelectedState(Button selectedRadioButton, List<Button> allRadioButtons) { for (Button radioButton : allRadioButtons) { boolean selected = radioButton.equals(selectedRadioButton); ButtonUtils.setSelectionAndFireEvent(radioButton, selected); if (selected) { radioButton.setFocus(); } } } private void updateVersionSelection(String version) { // select the version in the combo box if the exist for (int i = 0; i < this.gradleVersionCombo.getItemCount(); ++i) { if (this.gradleVersionCombo.getItem(i).equals(version)) { this.gradleVersionCombo.select(i); return; } } // otherwise, create a new entry in the combo and select it int index = this.gradleVersionCombo.getItemCount(); this.gradleVersionCombo.add(version, index); this.gradleVersionCombo.select(index); } @Override public void performApply(ILaunchConfigurationWorkingCopy configuration) { GradleRunConfigurationAttributes.applyGradleDistribution(getSelection().toGradleDistribution(), configuration); GradleRunConfigurationAttributes.applyOverrideBuildSettings(this.overrideBuildSettingsCheckbox.getSelection(), configuration); GradleRunConfigurationAttributes.applyOfflineMode(this.offlineModeCheckbox.getSelection(), configuration); GradleRunConfigurationAttributes.applyBuildScansEnabled(this.buildScansEnabledCheckbox.getSelection(), configuration); } @Override public boolean isValid(ILaunchConfiguration launchConfig) { GradleDistributionWrapper gradleDistribution = getSelection(); Optional<String> error = this.gradleDistributionValidator.validate(gradleDistribution); setErrorMessage(error.orNull()); return !error.isPresent(); } private GradleDistributionWrapper getSelection() { if (this.useGradleWrapperOption.getSelection()) { return GradleDistributionWrapper.from(DistributionType.WRAPPER, null); } else if (this.useLocalInstallationDirOption.getSelection()) { return GradleDistributionWrapper.from(DistributionType.LOCAL_INSTALLATION, this.localInstallationDirText.getText()); } else if (this.useRemoteDistributionUriOption.getSelection()) { return GradleDistributionWrapper.from(DistributionType.REMOTE_DISTRIBUTION, this.remoteDistributionUriText.getText()); } else if (this.useGradleVersionOption.getSelection()) { return getGradleDistributionWrapper(); } else { throw new IllegalStateException("No Gradle distribution selected."); } } private GradleDistributionWrapper getGradleDistributionWrapper() { int selectionIndex = this.gradleVersionCombo.getSelectionIndex(); return GradleDistributionWrapper.from(DistributionType.VERSION, Strings.emptyToNull(selectionIndex == -1 || !Strings.isNullOrEmpty(this.gradleVersionCombo.getText()) ? this.gradleVersionCombo.getText() : this.gradleVersionCombo.getItem(selectionIndex))); } @Override public void setDefaults(ILaunchConfigurationWorkingCopy configuration) { // leave the controls empty } @Override public void dispose() { this.defaultFont.dispose(); super.dispose(); } /** * Listener implementation to update the dialog buttons and messages. */ private class DialogUpdater extends SelectionAdapter implements ModifyListener { @Override public void widgetSelected(SelectionEvent e) { updateLaunchConfigurationDialog(); } @Override public void modifyText(ModifyEvent e) { updateLaunchConfigurationDialog(); } } }