/* * Copyright 2003-2017 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package jetbrains.mps.ide.generator; import com.intellij.ide.ui.UISettings; import com.intellij.openapi.options.ConfigurationException; import com.intellij.openapi.options.SearchableConfigurable; import com.intellij.ui.IdeBorderFactory; import jetbrains.mps.InternalFlag; import jetbrains.mps.generator.GenerationOptions; import jetbrains.mps.generator.GenerationSettingsProvider; import jetbrains.mps.generator.IModifiableGenerationSettings; import jetbrains.mps.icons.MPSIcons.Nodes; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.AbstractButton; import javax.swing.BoxLayout; import javax.swing.ButtonGroup; import javax.swing.Icon; import javax.swing.JCheckBox; import javax.swing.JComponent; import javax.swing.JFormattedTextField; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JRadioButton; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.text.DefaultFormatter; import java.awt.Color; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.event.ItemListener; import java.text.ParseException; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; class GenerationSettingsPreferencesPage implements SearchableConfigurable { private final JPanel myPage; private final JCheckBox mySaveTransientModelsCheckBox = new JCheckBox("Save transient models on generation"); private final JCheckBox myCheckModelsBeforeGenerationCheckBox = new JCheckBox("Check models for errors before generation"); private final JCheckBox myStrictMode = new JCheckBox("Strict mode"); private final JCheckBox myUseNewGenerator = new JCheckBox("Generate in parallel."); private final JFormattedTextField myNumberOfParallelThreads = new JFormattedTextField(new RangeDecimalFormatter(2, 32)); private final JCheckBox myIncremental = new JCheckBox("Incremental generation"); private final JCheckBox myIncrementalCache = new JCheckBox("Cache intermediate models"); private final JCheckBox myInplaceTransform = new JCheckBox("Apply transformations in place"); private final JCheckBox myDebugIncrementalDependencies = new JCheckBox("Debug generation dependencies"); private final JCheckBox myAvoidDynamicRefs = new JCheckBox("Resort to static references"); private JRadioButton myTraceNone = new JRadioButton("None"); private JRadioButton myTraceSteps = new JRadioButton("Generation steps only"); private JRadioButton myTraceLanguages = new JRadioButton("Time spent in language generators"); private JRadioButton myTraceTypes = new JRadioButton("Time spent in types calculation"); private JCheckBox myShowInfo = new JCheckBox("Show informational messages"); private JCheckBox myShowWarnings = new JCheckBox("Show warnings"); private JCheckBox myKeepModelsWithWarnings = new JCheckBox("Keep transient models with warnings"); private JCheckBox myShowBadChildWarnings = new JCheckBox("Warn when child cannot be placed into role"); private JCheckBox myLimitNumberOfModels = new JCheckBox("Maximum number of transient models to keep:"); private JFormattedTextField myNumberOfModelsToKeep = new JFormattedTextField(new RangeDecimalFormatter(0, 1000)); private JCheckBox myGenerateDebugInfo = new JCheckBox("Generate debug information"); private JLabel myStatusLabel; private final ItemListener myStatusUpdater = e -> updateStatus(); private final IModifiableGenerationSettings myGenerationSettings; private final ButtonSelectStateTracker myButtonState = new ButtonSelectStateTracker(); public GenerationSettingsPreferencesPage() { myGenerationSettings = GenerationSettingsProvider.getInstance().getGenerationSettings(); reset(); myPage = createPage(); myButtonState.reset(); myAvoidDynamicRefs.setToolTipText("Best effort to use static references, not dynamic, when target is referenced by name/resolveInfo"); } public String getName() { return "Generation"; } public Icon getIcon() { return Nodes.Generator; } @Override public JComponent createComponent() { return myPage; } public JPanel createPage() { JPanel myMainPanel = new JPanel(new GridBagLayout()); GridBagConstraints c = new GridBagConstraints(); c.gridx = 0; c.weightx = 1; c.fill = GridBagConstraints.BOTH; c.gridy = 0; myMainPanel.add(createOptionsPanel(), c); c.gridy++; myMainPanel.add(createReportingPanel(), c); c.gridy++; myMainPanel.add(createTraceLevelPanel(), c); c.gridy++; myMainPanel.add(createTextGenPanel(), c); c.gridy++; c.weighty = 1; myMainPanel.add(new JPanel(), c); c.gridy++; c.weighty = 0; myStatusLabel = new JLabel(); myMainPanel.add(myStatusLabel, c); updateStatus(); return myMainPanel; } private JPanel createOptionsPanel() { JPanel optionsPanel = new JPanel(new GridBagLayout()); GridBagConstraints c = new GridBagConstraints(); c.weightx = 1; c.gridx = 0; c.ipady = 2; c.fill = GridBagConstraints.BOTH; optionsPanel.add(mySaveTransientModelsCheckBox, c); optionsPanel.add(myCheckModelsBeforeGenerationCheckBox, c); optionsPanel.add(myStrictMode, c); c.ipady = 0; optionsPanel.add(createParallelGenerationGroup(), c); c.ipady = 2; optionsPanel.add(myIncremental, c); c.insets.left = 16; optionsPanel.add(myIncrementalCache, c); if (InternalFlag.isInternalMode()) { optionsPanel.add(myDebugIncrementalDependencies, c); } c.insets.left = 0; optionsPanel.add(myInplaceTransform, c); optionsPanel.add(myAvoidDynamicRefs, c); myButtonState.track(mySaveTransientModelsCheckBox, myCheckModelsBeforeGenerationCheckBox, myStrictMode, myInplaceTransform); myButtonState.track(myAvoidDynamicRefs); myButtonState.track(myDebugIncrementalDependencies, myIncremental, myIncrementalCache); final ChangeListener listener = new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { myIncremental.setEnabled(myStrictMode.isSelected()); myIncrementalCache.setEnabled(myStrictMode.isSelected() && myIncremental.isSelected()); if (InternalFlag.isInternalMode()) { myDebugIncrementalDependencies.setEnabled(myStrictMode.isSelected() && myIncremental.isSelected()); } } }; myStrictMode.addChangeListener(listener); myIncremental.addChangeListener(listener); optionsPanel.setBorder(IdeBorderFactory.createTitledBorder("General")); mySaveTransientModelsCheckBox.addItemListener(myStatusUpdater); myInplaceTransform.addItemListener(myStatusUpdater); myIncremental.addItemListener(myStatusUpdater); return optionsPanel; } private JPanel createParallelGenerationGroup() { JPanel parallelGen = new JPanel(new GridBagLayout()); GridBagConstraints c = new GridBagConstraints(); c.gridy = 0; parallelGen.add(myUseNewGenerator, c); final ChangeListener listener = new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { myUseNewGenerator.setEnabled(myStrictMode.isSelected()); myNumberOfParallelThreads.setEditable(myUseNewGenerator.isSelected() && myStrictMode.isSelected()); } }; myStrictMode.addChangeListener(listener); myUseNewGenerator.addChangeListener(listener); c.insets.left = 7; parallelGen.add(new JLabel("Use"), c); c.insets.left = 3; myNumberOfParallelThreads.setColumns(2); parallelGen.add(myNumberOfParallelThreads, c); c.insets.left = 2; parallelGen.add(new JLabel("cores"), c); c.weightx = 1; parallelGen.add(new JPanel(), c); parallelGen.setToolTipText(String.format("This computer has %d processors", Runtime.getRuntime().availableProcessors())); myButtonState.track(myUseNewGenerator); return parallelGen; } private JPanel createReportingPanel() { JPanel panel = new JPanel(new GridBagLayout()); GridBagConstraints c = new GridBagConstraints(); c.weightx = 1; c.gridx = 0; c.ipady = 2; c.fill = GridBagConstraints.BOTH; panel.add(myShowInfo, c); panel.add(myShowWarnings, c); c.insets.left = 16; panel.add(myKeepModelsWithWarnings, c); panel.add(myShowBadChildWarnings, c); myButtonState.track(myShowInfo, myShowWarnings, myKeepModelsWithWarnings, myShowBadChildWarnings); final ChangeListener listener = new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { myKeepModelsWithWarnings.setEnabled(myShowWarnings.isSelected()); myShowBadChildWarnings.setEnabled(myShowWarnings.isSelected()); } }; myShowWarnings.addChangeListener(listener); c.insets.left = 0; c.ipady = 0; panel.add(createLinkErrorsGroup(), c); panel.setBorder(IdeBorderFactory.createTitledBorder("Error reporting")); return panel; } private JPanel createLinkErrorsGroup() { JPanel group = new JPanel(new GridBagLayout()); GridBagConstraints c = new GridBagConstraints(); c.gridy = 0; group.add(myLimitNumberOfModels, c); final ChangeListener listener = new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { myNumberOfModelsToKeep.setEditable(myLimitNumberOfModels.isSelected()); } }; myLimitNumberOfModels.addChangeListener(listener); group.add(myLimitNumberOfModels, c); myNumberOfModelsToKeep.setColumns(3); c.insets.left = 5; group.add(myNumberOfModelsToKeep, c); c.weightx = 1; group.add(new JPanel(), c); myButtonState.track(myLimitNumberOfModels); return group; } private JPanel createTraceLevelPanel() { final ButtonGroup group = new ButtonGroup(); group.add(myTraceNone); group.add(myTraceSteps); group.add(myTraceLanguages); group.add(myTraceTypes); myButtonState.track(myTraceNone, myTraceSteps, myTraceLanguages, myTraceTypes); JPanel gotoPanel = new JPanel(); gotoPanel.setLayout(new BoxLayout(gotoPanel, BoxLayout.Y_AXIS)); gotoPanel.add(myTraceNone); gotoPanel.add(myTraceSteps); gotoPanel.add(myTraceLanguages); gotoPanel.add(myTraceTypes); gotoPanel.setBorder(IdeBorderFactory.createTitledBorder("Model generation performance report")); return gotoPanel; } private JPanel createTextGenPanel() { JPanel textgenPanel = new JPanel(); textgenPanel.setLayout(new BoxLayout(textgenPanel, BoxLayout.Y_AXIS)); textgenPanel.add(myGenerateDebugInfo); textgenPanel.setBorder(IdeBorderFactory.createTitledBorder("TextGen options")); myButtonState.track(myGenerateDebugInfo); return textgenPanel; } public boolean validate() { return true; } @Override public void apply() throws ConfigurationException { myGenerationSettings.setSaveTransientModels(mySaveTransientModelsCheckBox.isSelected()); myGenerationSettings.setCheckModelsBeforeGeneration(myCheckModelsBeforeGenerationCheckBox.isSelected()); myGenerationSettings.setParallelGenerator(myUseNewGenerator.isSelected()); myGenerationSettings.setStrictMode(myStrictMode.isSelected()); myGenerationSettings.setNumberOfParallelThreads((Integer) myNumberOfParallelThreads.getValue()); myGenerationSettings.setPerformanceTracingLevel(getTracingLevel()); myGenerationSettings.setShowInfo(myShowInfo.isSelected()); myGenerationSettings.setShowWarnings(myShowWarnings.isSelected()); myGenerationSettings.setKeepModelsWithWarnings(myKeepModelsWithWarnings.isSelected()); myGenerationSettings.setShowBadChildWarning(myShowBadChildWarnings.isSelected()); myGenerationSettings.setNumberOfModelsToKeep(getNumberOfModelsToKeep()); myGenerationSettings.setIncremental(myIncremental.isSelected()); myGenerationSettings.setIncrementalUseCache(myIncrementalCache.isSelected()); if (InternalFlag.isInternalMode()) { myGenerationSettings.setDebugIncrementalDependencies(myDebugIncrementalDependencies.isSelected()); } myGenerationSettings.enableInplaceTransformations(myInplaceTransform.isSelected()); myGenerationSettings.setCreateStaticReferences(myAvoidDynamicRefs.isSelected()); myGenerationSettings.setGenerateDebugInfo(myGenerateDebugInfo.isSelected()); myButtonState.reset(); // memorize the new state UISettings.getInstance().fireUISettingsChanged(); } private int getTracingLevel() { return myTraceTypes.isSelected() ? GenerationOptions.TRACE_TYPES : myTraceLanguages.isSelected() ? GenerationOptions.TRACE_LANGS : myTraceSteps.isSelected() ? GenerationOptions.TRACE_STEPS : GenerationOptions.TRACE_OFF; } private int getNumberOfModelsToKeep() { return myLimitNumberOfModels.isSelected() ? (Integer) myNumberOfModelsToKeep.getValue() : -1; } @Override public boolean isModified() { return myButtonState.isStateModified() || myGenerationSettings.getNumberOfModelsToKeep() != getNumberOfModelsToKeep() || myGenerationSettings.getNumberOfParallelThreads() != (Integer) myNumberOfParallelThreads.getValue(); } @Override public void disposeUIResources() { } @Override public void reset() { mySaveTransientModelsCheckBox.setSelected(myGenerationSettings.isSaveTransientModels()); myCheckModelsBeforeGenerationCheckBox.setSelected(myGenerationSettings.isCheckModelsBeforeGeneration()); myUseNewGenerator.setSelected(myGenerationSettings.isParallelGenerator()); myIncremental.setSelected(myGenerationSettings.isIncremental()); myIncrementalCache.setSelected(myGenerationSettings.isIncrementalUseCache()); if (InternalFlag.isInternalMode()) { myDebugIncrementalDependencies.setSelected(myGenerationSettings.isDebugIncrementalDependencies()); myDebugIncrementalDependencies.setEnabled(myGenerationSettings.isStrictMode() && myGenerationSettings.isIncremental()); } myInplaceTransform.setSelected(myGenerationSettings.useInplaceTransformations()); myAvoidDynamicRefs.setSelected(myGenerationSettings.createStaticReferences()); myStrictMode.setSelected(myGenerationSettings.isStrictMode()); myUseNewGenerator.setEnabled(myGenerationSettings.isStrictMode()); myIncremental.setEnabled(myGenerationSettings.isStrictMode()); myIncrementalCache.setEnabled(myGenerationSettings.isStrictMode() && myGenerationSettings.isIncremental()); myNumberOfParallelThreads.setEditable(myGenerationSettings.isParallelGenerator() && myGenerationSettings.isStrictMode()); myNumberOfParallelThreads.setValue(myGenerationSettings.getNumberOfParallelThreads()); myShowInfo.setSelected(myGenerationSettings.isShowInfo()); myShowWarnings.setSelected(myGenerationSettings.isShowWarnings()); myKeepModelsWithWarnings.setEnabled(myGenerationSettings.isShowWarnings()); myKeepModelsWithWarnings.setSelected(myGenerationSettings.isKeepModelsWithWarnings()); myShowBadChildWarnings.setEnabled(myGenerationSettings.isShowWarnings()); myShowBadChildWarnings.setSelected(myGenerationSettings.isShowBadChildWarning()); myNumberOfModelsToKeep.setEditable(myGenerationSettings.getNumberOfModelsToKeep() != -1); myNumberOfModelsToKeep.setValue(myGenerationSettings.getNumberOfModelsToKeep() == -1 ? 16 : myGenerationSettings.getNumberOfModelsToKeep()); myLimitNumberOfModels.setSelected(myGenerationSettings.getNumberOfModelsToKeep() != -1); myGenerateDebugInfo.setSelected(myGenerationSettings.isGenerateDebugInfo()); final JRadioButton[] allbuttons = {myTraceNone, myTraceSteps, myTraceLanguages, myTraceTypes}; allbuttons[myGenerationSettings.getPerformanceTracingLevel()].setSelected(true); myButtonState.reset(); // memorize the new state } void updateStatus() { myStatusLabel.setVisible(false); ArrayList<String> messages = new ArrayList<>(); if (myInplaceTransform.isSelected() && mySaveTransientModelsCheckBox.isSelected()) { messages.add("Warning: using in-place together with transient models may slow down generation process significantly"); } if (myIncremental.isSelected()) { messages.add("Warning: incremental generation is deprecated and hardly useful functionality"); } if (!messages.isEmpty()) { myStatusLabel.setText(String.format("<html>%s</html>", String.join("<br/>", messages))); myStatusLabel.setBackground(Color.yellow.brighter()); myStatusLabel.setOpaque(true); myStatusLabel.setVisible(true); } } @NotNull @Override public String getId() { return "generator.manager"; } @Nullable @Override public Runnable enableSearch(String option) { return null; } @Nls @Override public String getDisplayName() { return "Generator"; } @Nullable @Override public String getHelpTopic() { return "preferences.generator"; } private class RangeDecimalFormatter extends DefaultFormatter { private final int myLo; private final int myHi; private RangeDecimalFormatter(int lo, int hi) { super(); setAllowsInvalid(true); setCommitsOnValidEdit(true); myLo = lo; myHi = hi; } @Override public Object stringToValue(String text) throws ParseException { try { int i = Integer.parseInt(text); if (i < myLo || i > myHi) { throw new ParseException(text, text.length() - 1); } return i; } catch (NumberFormatException e) { throw new ParseException(text, 0); } } @Override public String valueToString(@Nullable Object value) throws ParseException { if (value == null) return null; return Integer.toString((Integer) value); } } private static class ButtonSelectStateTracker { private final Map<AbstractButton,Boolean> myButtonStates = new HashMap<AbstractButton, Boolean>(); public ButtonSelectStateTracker track(AbstractButton... buttons) { for (AbstractButton btn : buttons) { myButtonStates.put(btn, btn.isSelected()); } return this; } public void reset() { for (Map.Entry<AbstractButton, Boolean> e : myButtonStates.entrySet()) { e.setValue(e.getKey().isSelected()); } } public boolean isStateModified() { for (Map.Entry<AbstractButton, Boolean> e : myButtonStates.entrySet()) { if (e.getKey().isSelected() != e.getValue()) { return true; } } return false; } } }