Improving TeaVM module configuration UI

This commit is contained in:
Alexey Andreev 2016-02-21 01:02:35 +03:00
parent 0f9014103e
commit 97e83c7d8f
4 changed files with 205 additions and 38 deletions

View File

@ -100,6 +100,7 @@
<option name="reportLocalVariables" value="true" /> <option name="reportLocalVariables" value="true" />
<option name="reportMethodParameters" value="true" /> <option name="reportMethodParameters" value="true" />
</inspection_tool> </inspection_tool>
<inspection_tool class="UndesirableClassUsage" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="UnnecessaryBoxing" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> <inspection_tool class="UnnecessaryBoxing" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="UnregisteredActivator" enabled="false" level="ERROR" enabled_by_default="false" /> <inspection_tool class="UnregisteredActivator" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="UnusedAssignment" enabled="true" level="WEAK WARNING" enabled_by_default="true"> <inspection_tool class="UnusedAssignment" enabled="true" level="WEAK WARNING" enabled_by_default="true">

View File

@ -22,11 +22,14 @@ import org.jetbrains.jps.model.ex.JpsElementChildRoleBase;
import org.jetbrains.jps.model.module.JpsModule; import org.jetbrains.jps.model.module.JpsModule;
public class TeaVMJpsConfiguration extends JpsElementBase<TeaVMJpsConfiguration> { public class TeaVMJpsConfiguration extends JpsElementBase<TeaVMJpsConfiguration> {
public static final JpsElementChildRole<TeaVMJpsConfiguration> ROLE = JpsElementChildRoleBase.create( private static final JpsElementChildRole<TeaVMJpsConfiguration> ROLE = JpsElementChildRoleBase.create(
"TeaVM configuration"); "TeaVM configuration");
private boolean enabled; private boolean enabled;
private String mainClass; private String mainClass;
private String targetDirectory; private String targetDirectory;
private boolean minifying = false;
private boolean sourceMapsFileGenerated = true;
private boolean sourceFilesCopied = true;
public boolean isEnabled() { public boolean isEnabled() {
return enabled; return enabled;
@ -52,6 +55,30 @@ public class TeaVMJpsConfiguration extends JpsElementBase<TeaVMJpsConfiguration>
this.targetDirectory = targetDirectory; this.targetDirectory = targetDirectory;
} }
public boolean isMinifying() {
return minifying;
}
public void setMinifying(boolean minifying) {
this.minifying = minifying;
}
public boolean isSourceMapsFileGenerated() {
return sourceMapsFileGenerated;
}
public void setSourceMapsFileGenerated(boolean sourceMapsFileGenerated) {
this.sourceMapsFileGenerated = sourceMapsFileGenerated;
}
public boolean isSourceFilesCopied() {
return sourceFilesCopied;
}
public void setSourceFilesCopied(boolean sourceFilesCopied) {
this.sourceFilesCopied = sourceFilesCopied;
}
public static TeaVMJpsConfiguration get(JpsModule module) { public static TeaVMJpsConfiguration get(JpsModule module) {
return module.getContainer().getChild(ROLE); return module.getContainer().getChild(ROLE);
} }
@ -73,5 +100,8 @@ public class TeaVMJpsConfiguration extends JpsElementBase<TeaVMJpsConfiguration>
enabled = modified.enabled; enabled = modified.enabled;
mainClass = modified.mainClass; mainClass = modified.mainClass;
targetDirectory = modified.targetDirectory; targetDirectory = modified.targetDirectory;
minifying = modified.minifying;
sourceMapsFileGenerated = modified.sourceMapsFileGenerated;
sourceFilesCopied = modified.sourceFilesCopied;
} }
} }

View File

@ -49,7 +49,7 @@ public class TeaVMConfigurable implements Configurable {
@Override @Override
public JComponent createComponent() { public JComponent createComponent() {
if (panel == null) { if (panel == null) {
panel = new TeaVMConfigurationPanel(); panel = new TeaVMConfigurationPanel(module.getProject());
} }
return panel; return panel;
} }

View File

@ -15,6 +15,19 @@
*/ */
package org.teavm.idea.ui; package org.teavm.idea.ui;
import com.intellij.ide.util.TreeClassChooser;
import com.intellij.ide.util.TreeClassChooserFactory;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import com.intellij.openapi.util.Computable;
import com.intellij.psi.PsiClass;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.PsiMethodUtil;
import com.intellij.ui.components.JBLabel;
import java.awt.Font;
import java.awt.GridBagConstraints; import java.awt.GridBagConstraints;
import java.awt.GridBagLayout; import java.awt.GridBagLayout;
import java.util.Arrays; import java.util.Arrays;
@ -24,31 +37,68 @@ import java.util.function.BiConsumer;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JCheckBox; import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel; import javax.swing.JPanel;
import javax.swing.JTextField;
import org.teavm.idea.jps.model.TeaVMJpsConfiguration; import org.teavm.idea.jps.model.TeaVMJpsConfiguration;
class TeaVMConfigurationPanel extends JPanel { class TeaVMConfigurationPanel extends JPanel {
private final JCheckBox enabledCheckBox = new JCheckBox("TeaVM enabled for this module"); private final JCheckBox enabledCheckBox = new JCheckBox("TeaVM enabled for this module");
private final JTextField mainClassField = new JTextField(); private final TextFieldWithBrowseButton mainClassField = new TextFieldWithBrowseButton(event -> chooseMainClass());
private final JTextField targetDirectoryField = new JTextField(); private final TextFieldWithBrowseButton targetDirectoryField = new TextFieldWithBrowseButton();
private final JComboBox<ComboBoxItem<Boolean>> minifyingField = new JComboBox<>(new DefaultComboBoxModel<>());
private final JComboBox<ComboBoxItem<Boolean>> sourceMapsField = new JComboBox<>(new DefaultComboBoxModel<>());;
private final JComboBox<ComboBoxItem<Boolean>> copySourcesField = new JComboBox<>(new DefaultComboBoxModel<>());
private final TeaVMJpsConfiguration initialConfiguration = new TeaVMJpsConfiguration(); private final TeaVMJpsConfiguration initialConfiguration = new TeaVMJpsConfiguration();
private final List<JComponent> editComponents = Arrays.asList(mainClassField, targetDirectoryField); private final Project project;
private final List<JComponent> editComponents = Arrays.asList(mainClassField, targetDirectoryField,
minifyingField, sourceMapsField, copySourcesField);
private final List<ComboBoxItem<Boolean>> minifiedOptions = Arrays.asList(new ComboBoxItem<>(false, "Readable"),
new ComboBoxItem<>(true, "Minified (obfuscated)"));
private final List<ComboBoxItem<Boolean>> sourceMapsOptions = Arrays.asList(new ComboBoxItem<>(true, "Generate"),
new ComboBoxItem<>(false, "Skip"));
private final List<ComboBoxItem<Boolean>> copySourcesOptions = Arrays.asList(new ComboBoxItem<>(true, "Copy"),
new ComboBoxItem<>(false, "Skip"));
private final List<Field<?>> fields = Arrays.asList( private final List<Field<?>> fields = Arrays.asList(
new Field<>(TeaVMJpsConfiguration::setEnabled, TeaVMJpsConfiguration::isEnabled, new Field<>(TeaVMJpsConfiguration::setEnabled, TeaVMJpsConfiguration::isEnabled,
enabledCheckBox::setSelected, enabledCheckBox::isSelected), enabledCheckBox::setSelected, enabledCheckBox::isSelected),
new Field<>(TeaVMJpsConfiguration::setMainClass, TeaVMJpsConfiguration::getMainClass, new Field<>(TeaVMJpsConfiguration::setMainClass, TeaVMJpsConfiguration::getMainClass,
mainClassField::setText, mainClassField::getText), mainClassField::setText, mainClassField::getText),
new Field<>(TeaVMJpsConfiguration::setTargetDirectory, TeaVMJpsConfiguration::getTargetDirectory, new Field<>(TeaVMJpsConfiguration::setTargetDirectory, TeaVMJpsConfiguration::getTargetDirectory,
targetDirectoryField::setText, targetDirectoryField::getText) targetDirectoryField::setText, targetDirectoryField::getText),
new Field<>(TeaVMJpsConfiguration::setMinifying, TeaVMJpsConfiguration::isMinifying,
value -> minifyingField.setSelectedIndex(value ? 1 : 0),
() -> minifiedOptions.get(minifyingField.getSelectedIndex()).value),
new Field<>(TeaVMJpsConfiguration::setSourceMapsFileGenerated,
TeaVMJpsConfiguration::isSourceMapsFileGenerated,
value -> sourceMapsField.setSelectedIndex(value ? 0 : 1),
() -> sourceMapsOptions.get(sourceMapsField.getSelectedIndex()).value),
new Field<>(TeaVMJpsConfiguration::setSourceFilesCopied,
TeaVMJpsConfiguration::isSourceFilesCopied,
value -> copySourcesField.setSelectedIndex(value ? 0 : 1),
() -> copySourcesOptions.get(copySourcesField.getSelectedIndex()).value)
); );
TeaVMConfigurationPanel() { TeaVMConfigurationPanel(Project project) {
this.project = project;
enabledCheckBox.addActionListener(event -> updateEnabledState()); enabledCheckBox.addActionListener(event -> updateEnabledState());
setupLayout(); setupLayout();
FileChooserDescriptor targetDirectoryChooserDescriptor = FileChooserDescriptorFactory
.createSingleFolderDescriptor();
targetDirectoryField.addBrowseFolderListener("Target Directory", "Please, select folder where TeaVM should"
+ "write generated JS files", project, targetDirectoryChooserDescriptor);
minifiedOptions.stream().forEach(minifyingField::addItem);
sourceMapsOptions.stream().forEach(sourceMapsField::addItem);
copySourcesOptions.stream().forEach(copySourcesField::addItem);
} }
private void setupLayout() { private void setupLayout() {
@ -56,26 +106,75 @@ class TeaVMConfigurationPanel extends JPanel {
GridBagConstraints constraints = new GridBagConstraints(); GridBagConstraints constraints = new GridBagConstraints();
constraints.gridwidth = GridBagConstraints.REMAINDER; constraints.gridwidth = GridBagConstraints.REMAINDER;
constraints.anchor = GridBagConstraints.BASELINE_LEADING;
constraints.insets.left = 5;
constraints.insets.right = 5;
constraints.insets.bottom = 25;
constraints.insets.top = 10;
constraints.weighty = 1;
add(enabledCheckBox, constraints); add(enabledCheckBox, constraints);
GridBagConstraints labelConstrains = new GridBagConstraints(); GridBagConstraints labelConstraints = new GridBagConstraints();
labelConstrains.gridwidth = GridBagConstraints.RELATIVE; labelConstraints.gridwidth = GridBagConstraints.REMAINDER;
labelConstrains.anchor = GridBagConstraints.BASELINE_TRAILING; labelConstraints.anchor = GridBagConstraints.BASELINE_LEADING;
labelConstraints.weightx = 1;
labelConstraints.weighty = 1;
labelConstraints.insets.left = 5;
labelConstraints.insets.right = 5;
GridBagConstraints fieldConstrains = new GridBagConstraints(); GridBagConstraints descriptionConstraints = (GridBagConstraints) labelConstraints.clone();
fieldConstrains.gridwidth = GridBagConstraints.REMAINDER; descriptionConstraints.fill = GridBagConstraints.BOTH;
fieldConstrains.fill = GridBagConstraints.HORIZONTAL; descriptionConstraints.anchor = GridBagConstraints.BASELINE_LEADING;
labelConstrains.anchor = GridBagConstraints.BASELINE_LEADING; descriptionConstraints.insets.top = 3;
labelConstrains.insets.right = 5;
add(new JLabel("Main class:"), labelConstrains); GridBagConstraints fieldConstraints = new GridBagConstraints();
add(mainClassField, fieldConstrains); fieldConstraints.gridwidth = GridBagConstraints.REMAINDER;
fieldConstraints.fill = GridBagConstraints.HORIZONTAL;
fieldConstraints.anchor = GridBagConstraints.BASELINE_LEADING;
fieldConstraints.weightx = 1;
fieldConstraints.weighty = 1;
fieldConstraints.insets.top = 5;
fieldConstraints.insets.bottom = 20;
fieldConstraints.insets.left = 10;
fieldConstraints.insets.right = 10;
add(new JLabel("Target directory:"), labelConstrains); add(bold(new JBLabel("Main class")), labelConstraints);
add(targetDirectoryField, fieldConstrains); add(mainClassField, fieldConstraints);
add(bold(new JBLabel("Target directory")), labelConstraints);
add(targetDirectoryField, fieldConstraints);
fieldConstraints.fill = GridBagConstraints.NONE;
add(bold(new JBLabel("Minification")), labelConstraints);
add(new JBLabel("Indicates whether TeaVM should minify (obfuscate) generated JavaScript."),
descriptionConstraints);
add(new JBLabel("It is highly desirable for production environment, since minified code is up to 3 "
+ "times smaller."), descriptionConstraints);
add(minifyingField, fieldConstraints);
add(bold(new JBLabel("Source maps")), labelConstraints);
add(new JBLabel("Indicates whether TeaVM should generate source maps. With source maps "
+ "you can debug code in the browser's devtools."), descriptionConstraints);
add(sourceMapsField, fieldConstraints);
add(bold(new JBLabel("Copy source code")), labelConstraints);
add(new JBLabel("Source maps require your server to provide source code."), descriptionConstraints);
add(new JBLabel("TeaVM can copy source code to the corresponding location, which is very convenient if "
+ "you are going to debug in the browser."), descriptionConstraints);
add(copySourcesField, fieldConstraints);
constraints.fill = GridBagConstraints.BOTH;
constraints.weighty = 100;
constraints.weightx = 1;
add(new JPanel(), constraints);
} }
public void load(TeaVMJpsConfiguration config) { private static JBLabel bold(JBLabel label) {
label.setFont(label.getFont().deriveFont(Font.BOLD));
return label;
}
void load(TeaVMJpsConfiguration config) {
if (config == null) { if (config == null) {
config = new TeaVMJpsConfiguration(); config = new TeaVMJpsConfiguration();
} }
@ -86,13 +185,37 @@ class TeaVMConfigurationPanel extends JPanel {
updateEnabledState(); updateEnabledState();
} }
public void save(TeaVMJpsConfiguration config) { void save(TeaVMJpsConfiguration config) {
for (Field<?> field : fields) { for (Field<?> field : fields) {
saveField(field, config); saveField(field, config);
} }
updateInitialConfiguration(config); updateInitialConfiguration(config);
} }
boolean isModified() {
return fields.stream().anyMatch(this::isFieldModified);
}
private <T> boolean isFieldModified(Field<T> field) {
return !Objects.equals(field.dataSupplier.apply(initialConfiguration), field.editSupplier.get());
}
private void updateInitialConfiguration(TeaVMJpsConfiguration config) {
for (Field<?> field : fields) {
copyField(field, config);
}
}
private void updateEnabledState() {
for (JComponent component : editComponents) {
component.setEnabled(enabledCheckBox.isSelected());
}
}
private <T> void copyField(Field<T> field, TeaVMJpsConfiguration config) {
field.dataConsumer.accept(initialConfiguration, field.dataSupplier.apply(config));
}
private <T> void loadField(Field<T> field, TeaVMJpsConfiguration config) { private <T> void loadField(Field<T> field, TeaVMJpsConfiguration config) {
field.editConsumer.accept(field.dataSupplier.apply(config)); field.editConsumer.accept(field.dataSupplier.apply(config));
} }
@ -101,33 +224,46 @@ class TeaVMConfigurationPanel extends JPanel {
field.dataConsumer.accept(config, field.editSupplier.get()); field.dataConsumer.accept(config, field.editSupplier.get());
} }
private void updateEnabledState() { private void chooseMainClass() {
for (JComponent component : editComponents) { TreeClassChooser chooser = TreeClassChooserFactory
component.setEnabled(enabledCheckBox.isSelected()); .getInstance(project)
.createWithInnerClassesScopeChooser("Choose main class",
GlobalSearchScope.allScope(project), this::isMainClass, null);
chooser.showDialog();
PsiClass cls = chooser.getSelected();
if (cls != null) {
mainClassField.setText(cls.getQualifiedName());
} }
} }
public boolean isModified() { private boolean isMainClass(PsiClass cls) {
return fields.stream().anyMatch(this::isFieldModified); return ApplicationManager.getApplication().runReadAction((Computable<Boolean>) () -> {
return PsiMethodUtil.MAIN_CLASS.value(cls) && PsiMethodUtil.hasMainMethod(cls);
});
} }
private <T> boolean isFieldModified(Field<T> field) { private static class ComboBoxItem<T> {
return !Objects.equals(field.dataSupplier.apply(initialConfiguration), field.editSupplier.get()); final T value;
private final String title;
ComboBoxItem(T value, String title) {
this.value = value;
this.title = title;
}
@Override
public String toString() {
return title;
}
} }
private void updateInitialConfiguration(TeaVMJpsConfiguration config) { private static class Field<T> {
initialConfiguration.setEnabled(config.isEnabled());
initialConfiguration.setMainClass(config.getMainClass());
initialConfiguration.setTargetDirectory(config.getTargetDirectory());
}
static class Field<T> {
final BiConsumer<TeaVMJpsConfiguration, T> dataConsumer; final BiConsumer<TeaVMJpsConfiguration, T> dataConsumer;
final Function<TeaVMJpsConfiguration, T> dataSupplier; final Function<TeaVMJpsConfiguration, T> dataSupplier;
final Consumer<T> editConsumer; final Consumer<T> editConsumer;
final Supplier<T> editSupplier; final Supplier<T> editSupplier;
public Field(BiConsumer<TeaVMJpsConfiguration, T> dataConsumer, Function<TeaVMJpsConfiguration, T> dataSupplier, Field(BiConsumer<TeaVMJpsConfiguration, T> dataConsumer, Function<TeaVMJpsConfiguration, T> dataSupplier,
Consumer<T> editConsumer, Supplier<T> editSupplier) { Consumer<T> editConsumer, Supplier<T> editSupplier) {
this.dataConsumer = dataConsumer; this.dataConsumer = dataConsumer;
this.dataSupplier = dataSupplier; this.dataSupplier = dataSupplier;