package com.intellij.lang.javascript.flex.projectStructure.ui; import com.intellij.compiler.options.CompilerUIConfigurable; import com.intellij.flex.model.bc.BuildConfigurationNature; import com.intellij.flex.model.bc.CompilerOptionInfo; import com.intellij.flex.model.bc.ValueSource; import com.intellij.lang.javascript.flex.FlexBundle; import com.intellij.lang.javascript.flex.FlexUtils; import com.intellij.lang.javascript.flex.projectStructure.FlexProjectLevelCompilerOptionsHolder; import com.intellij.lang.javascript.flex.projectStructure.model.CompilerOptions; import com.intellij.lang.javascript.flex.projectStructure.model.CompilerOptionsListener; import com.intellij.lang.javascript.flex.projectStructure.model.FlexBuildConfigurationManager; import com.intellij.lang.javascript.flex.projectStructure.model.ModifiableCompilerOptions; import com.intellij.lang.javascript.flex.projectStructure.options.BCUtils; import com.intellij.lang.javascript.flex.sdk.FlexSdkType2; import com.intellij.lang.javascript.flex.sdk.FlexmojosSdkType; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.fileChooser.FileChooserDescriptor; import com.intellij.openapi.module.Module; import com.intellij.openapi.options.ConfigurationException; import com.intellij.openapi.options.ShowSettingsUtil; import com.intellij.openapi.project.Project; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.ui.ComponentWithBrowseButton; import com.intellij.openapi.ui.NamedConfigurable; import com.intellij.openapi.ui.TextFieldWithBrowseButton; import com.intellij.openapi.util.ActionCallback; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.wm.IdeFocusManager; import com.intellij.ui.*; import com.intellij.ui.navigation.History; import com.intellij.ui.navigation.Place; import com.intellij.ui.treeStructure.treetable.ListTreeTableModel; import com.intellij.ui.treeStructure.treetable.TreeTable; import com.intellij.ui.treeStructure.treetable.TreeTableModel; import com.intellij.ui.treeStructure.treetable.TreeTableTree; import com.intellij.util.EventDispatcher; import com.intellij.util.PathUtil; import com.intellij.util.PlatformIcons; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.ui.AbstractTableCellEditor; import com.intellij.util.ui.ColumnInfo; import com.intellij.util.ui.UIUtil; import com.intellij.util.ui.tree.TreeUtil; import gnu.trove.THashMap; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.DocumentEvent; import javax.swing.event.HyperlinkEvent; import javax.swing.table.TableCellEditor; import javax.swing.table.TableCellRenderer; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreeCellRenderer; import javax.swing.tree.TreePath; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.*; import java.util.List; import static com.intellij.lang.javascript.flex.projectStructure.model.CompilerOptions.ResourceFilesMode; public class CompilerOptionsConfigurable extends NamedConfigurable<CompilerOptions> implements Place.Navigator { public static final String TAB_NAME = FlexBundle.message("bc.tab.compiler.options.display.name"); public static final String CONDITIONAL_COMPILER_DEFINITION_NAME = "FlexCompilerOptions.ConditionalCompilerDefinitionName"; public enum Location { AdditionalConfigFile("additional-config-file"), FilesToIncludeInSwc("files-to-include-in-swc"), ConditionalCompilerDefinition("doesn't matter"); public final String errorId; Location(final String errorId) { this.errorId = errorId; } } private JPanel myMainPanel; private TreeTable myTreeTable; private NonFocusableCheckBox myShowAllOptionsCheckBox; private JLabel myInheritProjectDefaultsLegend; private JLabel myInheritModuleDefaultsLegend; private JButton myProjectDefaultsButton; private JButton myModuleDefaultsButton; private JPanel myResourcesPanel; private JCheckBox myCopyResourceFilesCheckBox; private JRadioButton myCopyAllResourcesRadioButton; private JRadioButton myRespectResourcePatternsRadioButton; private HoverHyperlinkLabel myResourcePatternsHyperlink; private JPanel myIncludeInSWCPanel; private TextFieldWithBrowseButton myIncludeInSWCField; private Collection<String> myFilesToIncludeInSWC; private JLabel myConfigFileLabel; private TextFieldWithBrowseButton myConfigFileTextWithBrowse; private JLabel myInheritedOptionsLabel; private JTextField myInheritedOptionsField; private JLabel myAdditionalOptionsLabel; private RawCommandLineEditor myAdditionalOptionsField; private JLabel myNoteLabel; private final Mode myMode; private final Module myModule; private final BuildConfigurationNature myNature; private final DependenciesConfigurable myDependenciesConfigurable; private final Project myProject; private final String myName; private final FlexBuildConfigurationManager myBCManager; private final FlexProjectLevelCompilerOptionsHolder myProjectLevelOptionsHolder; private final ModifiableCompilerOptions myModel; private final Map<String, String> myCurrentOptions; private boolean myMapModified; private final EventDispatcher<UserActivityListener> myUserActivityDispatcher; private boolean myFreeze; private final Collection<OptionsListener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList(); private final Disposable myDisposable = Disposer.newDisposable(); private static final String UNKNOWN_SDK_VERSION = "100"; private enum Mode {BC, Module, Project} public interface OptionsListener extends EventListener { void configFileChanged(String additionalConfigFilePath); void additionalOptionsChanged(String additionalOptions); } public CompilerOptionsConfigurable(final Module module, final BuildConfigurationNature nature, final DependenciesConfigurable dependenciesConfigurable, final ModifiableCompilerOptions model) { this(Mode.BC, module, module.getProject(), nature, dependenciesConfigurable, model); dependenciesConfigurable.addSdkChangeListener(new ChangeListener() { public void stateChanged(final ChangeEvent e) { updateTreeTable(); } }); } private CompilerOptionsConfigurable(final Mode mode, final Module module, final Project project, final BuildConfigurationNature nature, final DependenciesConfigurable dependenciesConfigurable, final ModifiableCompilerOptions model) { myMode = mode; myModule = module; myProject = project; myNature = nature; myDependenciesConfigurable = dependenciesConfigurable; myName = myMode == Mode.BC ? TAB_NAME : myMode == Mode.Module ? FlexBundle.message("default.compiler.options.for.module.title", module.getName()) : FlexBundle.message("default.compiler.options.for.project.title", project.getName()); myBCManager = myMode == Mode.BC ? FlexBuildConfigurationManager.getInstance(module) : null; myProjectLevelOptionsHolder = FlexProjectLevelCompilerOptionsHolder.getInstance(project); myModel = model; myCurrentOptions = new THashMap<>(); myFilesToIncludeInSWC = Collections.emptyList(); myShowAllOptionsCheckBox.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent e) { updateTreeTable(); } }); myInheritModuleDefaultsLegend.setVisible(myMode == Mode.BC); myInheritProjectDefaultsLegend.setVisible(myMode == Mode.BC || myMode == Mode.Module); myResourcesPanel.setVisible(myMode == Mode.BC && BCUtils.canHaveResourceFiles(myNature)); myCopyResourceFilesCheckBox.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent e) { updateResourcesControls(); } }); myResourcePatternsHyperlink.addHyperlinkListener(new HyperlinkAdapter() { protected void hyperlinkActivated(final HyperlinkEvent e) { ShowSettingsUtil.getInstance().editConfigurable(project, new CompilerUIConfigurable(module.getProject())); } }); myIncludeInSWCPanel.setVisible(myMode == Mode.BC && myNature.isLib()); myIncludeInSWCField.getTextField().setEditable(false); myIncludeInSWCField.setButtonIcon(PlatformIcons.OPEN_EDIT_DIALOG_ICON); myIncludeInSWCField.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent e) { final List<StringBuilder> value = new ArrayList<>(); for (String path : myFilesToIncludeInSWC) { value.add(new StringBuilder(path)); } final RepeatableValueDialog dialog = new RepeatableValueDialog(module.getProject(), FlexBundle.message("items.to.include.in.swc.dialog.title"), value, CompilerOptionInfo.INCLUDE_FILE_INFO_FOR_UI); if (dialog.showAndGet()) { final List<StringBuilder> newValue = dialog.getCurrentList(); myFilesToIncludeInSWC = new ArrayList<>(newValue.size()); for (StringBuilder path : newValue) { myFilesToIncludeInSWC.add(path.toString()); } updateFilesToIncludeInSWCText(); } } }); initButtonsAndAdditionalOptions(); // seems we don't need it for small amount of options myShowAllOptionsCheckBox.setSelected(true); myShowAllOptionsCheckBox.setVisible(false); UserActivityWatcher watcher = new UserActivityWatcher(); watcher.register(myMainPanel); myUserActivityDispatcher = EventDispatcher.create(UserActivityListener.class); watcher.addUserActivityListener(new UserActivityListener() { @Override public void stateChanged() { if (myFreeze) { return; } myUserActivityDispatcher.getMulticaster().stateChanged(); } }, myDisposable); } public void addUserActivityListener(final UserActivityListener listener, final Disposable disposable) { myUserActivityDispatcher.addListener(listener, disposable); } public void removeUserActivityListeners() { for (UserActivityListener listener : myUserActivityDispatcher.getListeners()) { myUserActivityDispatcher.removeListener(listener); } } private void updateFilesToIncludeInSWCText() { final String s = StringUtil.join(myFilesToIncludeInSWC, path -> PathUtil.getFileName(path), ", "); myIncludeInSWCField.setText(s); } private void initButtonsAndAdditionalOptions() { if (myMode == Mode.BC || myMode == Mode.Module) { final CompilerOptionsListener optionsListener = new CompilerOptionsListener() { public void optionsInTableChanged() { updateTreeTable(); } public void additionalOptionsChanged() { updateAdditionalOptionsControls(); } }; if (myMode == Mode.BC) { myBCManager.getModuleLevelCompilerOptions().addOptionsListener(optionsListener, myDisposable); } myProjectLevelOptionsHolder.getProjectLevelCompilerOptions().addOptionsListener(optionsListener, myDisposable); } final ActionListener projectDefaultsListener = new ActionListener() { public void actionPerformed(final ActionEvent e) { ModifiableCompilerOptions compilerOptions = myProjectLevelOptionsHolder.getProjectLevelCompilerOptions(); final CompilerOptionsConfigurable configurable = new CompilerOptionsConfigurable(Mode.Project, null, myProject, myNature, myDependenciesConfigurable, compilerOptions); ShowSettingsUtil.getInstance().editConfigurable(myProject, configurable); } }; final ActionListener moduleDefaultsListener = new ActionListener() { public void actionPerformed(final ActionEvent e) { ModifiableCompilerOptions compilerOptions = myBCManager.getModuleLevelCompilerOptions(); final CompilerOptionsConfigurable configurable = new CompilerOptionsConfigurable(Mode.Module, myModule, myProject, myNature, myDependenciesConfigurable, compilerOptions); ShowSettingsUtil.getInstance().editConfigurable(myProject, configurable); } }; myConfigFileTextWithBrowse.addBrowseFolderListener(null, null, myProject, FlexUtils.createFileChooserDescriptor("xml")); myConfigFileTextWithBrowse.getTextField().getDocument().addDocumentListener(new DocumentAdapter() { protected void textChanged(final DocumentEvent e) { updateAdditionalOptionsControls(); fireConfigFileChanged(); } }); myConfigFileLabel.setVisible(myMode == Mode.BC); myConfigFileTextWithBrowse.setVisible(myMode == Mode.BC); myInheritedOptionsLabel.setVisible(myMode == Mode.BC || myMode == Mode.Module); myInheritedOptionsField.setVisible(myMode == Mode.BC || myMode == Mode.Module); final String labelText = myMode == Mode.BC ? "Additional compiler options:" : myMode == Mode.Module ? "Default options for module:" : "Default options for project:"; myAdditionalOptionsLabel.setText(labelText); myAdditionalOptionsField.setDialogCaption(StringUtil.capitalizeWords(labelText, true)); myAdditionalOptionsField.getTextField().getDocument().addDocumentListener(new DocumentAdapter() { protected void textChanged(final DocumentEvent e) { updateAdditionalOptionsControls(); fireAdditionalOptionsChanged(); } }); myNoteLabel.setIcon(UIUtil.getBalloonInformationIcon()); myProjectDefaultsButton.addActionListener(projectDefaultsListener); myProjectDefaultsButton.setVisible(myMode == Mode.BC || myMode == Mode.Module); myModuleDefaultsButton.addActionListener(moduleDefaultsListener); myModuleDefaultsButton.setVisible(myMode == Mode.BC); } public void addAdditionalOptionsListener(final OptionsListener listener) { myListeners.add(listener); } private void fireConfigFileChanged() { for (OptionsListener listener : myListeners) { listener.configFileChanged(FileUtil.toSystemIndependentName(myConfigFileTextWithBrowse.getText().trim())); } } private void fireAdditionalOptionsChanged() { for (OptionsListener listener : myListeners) { listener.additionalOptionsChanged(myAdditionalOptionsField.getText().trim()); } } @Nls public String getDisplayName() { return myName; } public void setDisplayName(final String name) { } public String getBannerSlogan() { return getDisplayName(); } public CompilerOptions getEditableObject() { return myModel; } public String getHelpTopic() { return "BuildConfigurationPage.CompilerOptions"; } public JComponent createOptionsPanel() { //TreeUtil.expandAll(myTreeTable.getTree()); return myMainPanel; } public boolean isModified() { if (myMapModified) return true; if (myModel.getResourceFilesMode() != getResourceFilesMode()) return true; if (!myModel.getFilesToIncludeInSWC().equals(myFilesToIncludeInSWC)) return true; if (!myModel.getAdditionalConfigFilePath().equals(FileUtil.toSystemIndependentName(myConfigFileTextWithBrowse.getText().trim()))) { return true; } if (!myModel.getAdditionalOptions().equals(myAdditionalOptionsField.getText().trim())) return true; return false; } private ResourceFilesMode getResourceFilesMode() { return !myCopyResourceFilesCheckBox.isVisible() || !myCopyResourceFilesCheckBox.isSelected() ? ResourceFilesMode.None : myCopyAllResourcesRadioButton.isSelected() ? ResourceFilesMode.All : ResourceFilesMode.ResourcePatterns; } public void apply() throws ConfigurationException { applyTo(myModel); } public void applyTo(final ModifiableCompilerOptions compilerOptions) { TableUtil.stopEditing(myTreeTable); compilerOptions.setAllOptions(myCurrentOptions); if (compilerOptions == myModel) { myMapModified = false; } compilerOptions.setResourceFilesMode(getResourceFilesMode()); compilerOptions.setFilesToIncludeInSWC(myFilesToIncludeInSWC); compilerOptions.setAdditionalConfigFilePath(FileUtil.toSystemIndependentName(myConfigFileTextWithBrowse.getText().trim())); compilerOptions.setAdditionalOptions(myAdditionalOptionsField.getText().trim()); } public void reset() { myFreeze = true; try { myCurrentOptions.clear(); myCurrentOptions.putAll(myModel.getAllOptions()); myMapModified = false; updateTreeTable(); final ResourceFilesMode mode = myModel.getResourceFilesMode(); myCopyResourceFilesCheckBox.setSelected(mode != ResourceFilesMode.None); myCopyAllResourcesRadioButton.setSelected(mode == ResourceFilesMode.None || mode == ResourceFilesMode.All); myRespectResourcePatternsRadioButton.setSelected(mode == ResourceFilesMode.ResourcePatterns); updateResourcesControls(); myFilesToIncludeInSWC = myModel.getFilesToIncludeInSWC(); updateFilesToIncludeInSWCText(); myConfigFileTextWithBrowse.setText(FileUtil.toSystemDependentName(myModel.getAdditionalConfigFilePath())); myAdditionalOptionsField.setText(myModel.getAdditionalOptions()); updateAdditionalOptionsControls(); } finally { myFreeze = false; } } public void disposeUIResources() { myListeners.clear(); Disposer.dispose(myDisposable); } private void createUIComponents() { myTreeTable = createTreeTable(); myResourcePatternsHyperlink = new HoverHyperlinkLabel("resource patterns"); } private TreeTable createTreeTable() { final DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode(); final TreeTableModel model = new ListTreeTableModel(rootNode, createColumns()); final TreeTable treeTable = new TreeTable(model); treeTable.getColumnModel().getColumn(1).setCellRenderer(createValueRenderer()); treeTable.getColumnModel().getColumn(1).setCellEditor(createValueEditor()); final TreeTableTree tree = treeTable.getTree(); tree.setRootVisible(false); tree.setLineStyleAngled(); tree.setShowsRootHandles(true); tree.setCellRenderer(createTreeCellRenderer()); treeTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); treeTable.setRowHeight(new JTextField("Fake").getPreferredSize().height + 3); //treeTable.setTableHeader(null); final DefaultActionGroup group = new DefaultActionGroup(); group.add(new RestoreDefaultValueAction(tree)); PopupHandler.installPopupHandler(treeTable, group, ActionPlaces.UNKNOWN, ActionManager.getInstance()); new TreeTableSpeedSearch(treeTable, o -> { final Object userObject = ((DefaultMutableTreeNode)o.getLastPathComponent()).getUserObject(); return userObject instanceof CompilerOptionInfo ? ((CompilerOptionInfo)userObject).DISPLAY_NAME : ""; }).setComparator(new SpeedSearchComparator(false)); return treeTable; } private TreeCellRenderer createTreeCellRenderer() { return new TreeCellRenderer() { private final JLabel myLabel = new JLabel(); public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { final Object userObject = ((DefaultMutableTreeNode)value).getUserObject(); if (!(userObject instanceof CompilerOptionInfo)) { // invisible root node return myLabel; } final CompilerOptionInfo info = (CompilerOptionInfo)userObject; myLabel.setText(info.DISPLAY_NAME); final ValueSource valueSource = getValueAndSource(info).second; renderAccordingToSource(myLabel, valueSource, selected); myLabel.setForeground(selected ? UIUtil.getTableSelectionForeground() : UIUtil.getTableForeground()); return myLabel; } }; } private static void renderAccordingToSource(final Component component, final ValueSource valueSource, final boolean selected) { final int boldOrPlain = valueSource == ValueSource.BC || valueSource == ValueSource.ProjectDefault ? Font.BOLD : Font.PLAIN; component.setFont(component.getFont().deriveFont(boldOrPlain)); component.setEnabled(selected || valueSource == ValueSource.BC || valueSource == ValueSource.ModuleDefault); } private TableCellRenderer createValueRenderer() { return new TableCellRenderer() { private final JLabel myLabel = new JLabel(); private final JCheckBox myCheckBox = new JCheckBox(); private final ComponentWithBrowseButton<JLabel> myLabelWithBrowse = new ComponentWithBrowseButton<>(new JLabel(), null); public Component getTableCellRendererComponent(JTable table, Object value, boolean selected, boolean hasFocus, int row, int column) { if (!(value instanceof CompilerOptionInfo)) { // invisible root node or group node myLabel.setText(""); return myLabel; } final CompilerOptionInfo info = (CompilerOptionInfo)value; final Pair<String, ValueSource> valueAndSource = getValueAndSource(info); switch (info.TYPE) { case Boolean: myCheckBox.setBackground(table.getBackground()); //myCheckBox.setForeground(UIUtil.getTableForeground()); //myCheckBox.setEnabled(moduleDefault || fromConfigFile || custom); myCheckBox.setSelected("true".equalsIgnoreCase(valueAndSource.first)); return myCheckBox; case String: case Int: case List: case IncludeClasses: case IncludeFiles: myLabel.setBackground(table.getBackground()); myLabel.setText(getPresentableSummary(valueAndSource.first, info)); renderAccordingToSource(myLabel, valueAndSource.second, false); return myLabel; case File: final JLabel label = myLabelWithBrowse.getChildComponent(); myLabelWithBrowse.setBackground(table.getBackground()); label.setText(FileUtil.toSystemDependentName(valueAndSource.first)); renderAccordingToSource(label, valueAndSource.second, false); return myLabelWithBrowse; case Group: default: assert false; return null; } } }; } private static String getPresentableSummary(final String rawValue, final CompilerOptionInfo info) { if (info.TYPE == CompilerOptionInfo.OptionType.List) { if (info.LIST_ELEMENTS.length == 1) { final String fixedSlashes = info.LIST_ELEMENTS[0].LIST_ELEMENT_TYPE == CompilerOptionInfo.ListElementType.File ? FileUtil.toSystemDependentName(rawValue) : rawValue; return fixedSlashes.replace(CompilerOptionInfo.LIST_ENTRIES_SEPARATOR, ", "); } if ("compiler.define".equals(info.ID)) { return rawValue.replace(CompilerOptionInfo.LIST_ENTRIES_SEPARATOR, ", ") .replace(CompilerOptionInfo.LIST_ENTRY_PARTS_SEPARATOR, "="); } final StringBuilder b = new StringBuilder(); for (String entry : StringUtil.split(rawValue, CompilerOptionInfo.LIST_ENTRIES_SEPARATOR)) { if (b.length() > 0) b.append(", "); b.append(entry.substring(0, entry.indexOf(CompilerOptionInfo.LIST_ENTRY_PARTS_SEPARATOR))); } return b.toString(); } return rawValue; } private TableCellEditor createValueEditor() { return new AbstractTableCellEditor() { //private CellEditorComponentWithBrowseButton<JTextField> myTextWithBrowse; //private LocalPathCellEditor myLocalPathCellEditor; private final JTextField myTextField = new JTextField(); private final TextFieldWithBrowseButton myTextWithBrowse = new TextFieldWithBrowseButton(); private final RepeatableValueEditor myRepeatableValueEditor = new RepeatableValueEditor(myProject); private final ExtensionAwareFileChooserDescriptor myFileChooserDescriptor = new ExtensionAwareFileChooserDescriptor(); private final JCheckBox myCheckBox = new JCheckBox(); { myTextWithBrowse.addBrowseFolderListener(null, null, myProject, myFileChooserDescriptor); myCheckBox.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent e) { TableUtil.stopEditing(myTreeTable); // apply new check box state immediately } }); } private Component myCurrentEditor; public Component getTableCellEditorComponent(final JTable table, Object value, boolean isSelected, final int row, int column) { assert value instanceof CompilerOptionInfo; final CompilerOptionInfo info = (CompilerOptionInfo)value; final Sdk sdk = myDependenciesConfigurable.getCurrentSdk(); final String sdkHome = sdk == null || sdk.getSdkType() == FlexmojosSdkType.getInstance() ? null : sdk.getHomePath(); final String optionValue = sdkHome == null ? getValueAndSource(info).first : getValueAndSource(info).first.replace(CompilerOptionInfo.FLEX_SDK_MACRO, sdkHome); switch (info.TYPE) { case Boolean: myCheckBox.setBackground(table.getBackground()); myCheckBox.setSelected("true".equalsIgnoreCase(optionValue)); myCurrentEditor = myCheckBox; break; case List: myRepeatableValueEditor.setInfoAndValue(info, optionValue); myCurrentEditor = myRepeatableValueEditor; break; case String: case Int: case IncludeClasses: case IncludeFiles: myTextField.setText(optionValue); myCurrentEditor = myTextField; break; case File: myFileChooserDescriptor.setAllowedExtensions(info.FILE_EXTENSION); myTextWithBrowse.setText(FileUtil.toSystemDependentName(optionValue)); myCurrentEditor = myTextWithBrowse; break; case Group: default: assert false; } return myCurrentEditor; } public Object getCellEditorValue() { if (myCurrentEditor == myCheckBox) { return String.valueOf(myCheckBox.isSelected()); } if (myCurrentEditor == myTextField) { return myTextField.getText().trim(); } if (myCurrentEditor == myTextWithBrowse) { final Sdk sdk = myDependenciesConfigurable.getCurrentSdk(); final String sdkHome = sdk == null || sdk.getSdkType() == FlexmojosSdkType.getInstance() ? null : sdk.getHomePath(); final String path = FileUtil.toSystemIndependentName(myTextWithBrowse.getText().trim()); return sdkHome == null ? path : path.replace(sdkHome, CompilerOptionInfo.FLEX_SDK_MACRO); } if (myCurrentEditor == myRepeatableValueEditor) { final Sdk sdk = myDependenciesConfigurable.getCurrentSdk(); final String sdkHome = sdk == null ? null : sdk.getHomePath(); final String value = myRepeatableValueEditor.getValue(); return sdkHome == null ? value : value.replace(sdkHome, CompilerOptionInfo.FLEX_SDK_MACRO); } assert false; return null; } }; } private ColumnInfo[] createColumns() { final ColumnInfo optionColumn = new ColumnInfo("Option") { public Object valueOf(final Object o) { final Object userObject = ((DefaultMutableTreeNode)o).getUserObject(); return userObject instanceof CompilerOptionInfo ? userObject : o; } public Class getColumnClass() { return TreeTableModel.class; } }; final ColumnInfo valueColumn = new ColumnInfo("Value") { public Object valueOf(Object o) { final Object userObject = ((DefaultMutableTreeNode)o).getUserObject(); return userObject instanceof CompilerOptionInfo && !((CompilerOptionInfo)userObject).isGroup() ? userObject : null; } public Class getColumnClass() { return CompilerOptionInfo.class; } //public TableCellRenderer getRenderer(Object o) { // return myValueRenderer; //} //public TableCellEditor getEditor(Object item) { // return myEditor; //} public boolean isCellEditable(Object o) { final Object userObject = ((DefaultMutableTreeNode)o).getUserObject(); return userObject instanceof CompilerOptionInfo && !((CompilerOptionInfo)userObject).isGroup(); } public void setValue(Object node, Object value) { final DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode)node; final CompilerOptionInfo info = (CompilerOptionInfo)treeNode.getUserObject(); final Pair<String, ValueSource> valueAndSource = getValueAndSource(info); // don't apply if user just clicked through the table without typing anything if (!value.equals(valueAndSource.first)) { myMapModified = true; myCurrentOptions.put(info.ID, (String)value); reloadNodeOrGroup(myTreeTable.getTree(), treeNode); } } }; return new ColumnInfo[]{optionColumn, valueColumn}; } private static void reloadNodeOrGroup(final JTree tree, final DefaultMutableTreeNode treeNode) { DefaultMutableTreeNode nodeToRefresh = treeNode; DefaultMutableTreeNode parent; while ((parent = ((DefaultMutableTreeNode)nodeToRefresh.getParent())).getUserObject() instanceof CompilerOptionInfo) { nodeToRefresh = parent; } ((DefaultTreeModel)tree.getModel()).reload(nodeToRefresh); } private void updateResourcesControls() { myCopyAllResourcesRadioButton.setEnabled(myCopyResourceFilesCheckBox.isSelected()); myRespectResourcePatternsRadioButton.setEnabled(myCopyResourceFilesCheckBox.isSelected()); myResourcePatternsHyperlink.setEnabled(myCopyResourceFilesCheckBox.isSelected()); } private void updateAdditionalOptionsControls() { final String projectOptions = myProjectLevelOptionsHolder.getProjectLevelCompilerOptions().getAdditionalOptions(); if (myMode == Mode.BC) { myInheritedOptionsField.setText(projectOptions + (projectOptions.isEmpty() ? "" : " ") + myBCManager.getModuleLevelCompilerOptions().getAdditionalOptions()); } else if (myMode == Mode.Module) { myInheritedOptionsField.setText(projectOptions); } myNoteLabel.setVisible(myMode == Mode.BC && (!myConfigFileTextWithBrowse.getText().trim().isEmpty() || !myInheritedOptionsField.getText().trim().isEmpty() || !myAdditionalOptionsField.getText().trim().isEmpty())); } private void updateTreeTable() { final TreeTableTree tree = myTreeTable.getTree(); final TreePath selectionPath = tree.getSelectionPath(); final List<TreePath> expandedPaths = TreeUtil.collectExpandedPaths(tree); final DefaultTreeModel treeModel = (DefaultTreeModel)tree.getModel(); final DefaultMutableTreeNode rootNode = (DefaultMutableTreeNode)treeModel.getRoot(); final CompilerOptionInfo[] optionInfos = CompilerOptionInfo.getRootInfos(); final boolean showAll = myShowAllOptionsCheckBox.isSelected(); updateChildNodes(rootNode, optionInfos, showAll); treeModel.reload(rootNode); TreeUtil.restoreExpandedPaths(tree, expandedPaths); tree.setSelectionPath(selectionPath); } private void updateChildNodes(final DefaultMutableTreeNode rootNode, final CompilerOptionInfo[] optionInfos, final boolean showAll) { int currentNodeNumber = 0; for (final CompilerOptionInfo info : optionInfos) { final boolean show = info.isGroup() || // empty group will be hidden later ((myMode != Mode.BC || info.isApplicable(getSdkVersion(), myNature)) && (showAll || !info.ADVANCED || hasCustomValue(info)) ); DefaultMutableTreeNode node = findChildNodeWithInfo(rootNode, info); if (show) { if (node == null) { node = new DefaultMutableTreeNode(info, info.isGroup()); rootNode.insert(node, currentNodeNumber); } currentNodeNumber++; if (info.isGroup()) { updateChildNodes(node, info.getChildOptionInfos(), showAll); if (node.getChildCount() == 0) { node.removeFromParent(); currentNodeNumber--; } } } else { if (node != null) { node.removeFromParent(); } } } } @Nullable private static DefaultMutableTreeNode findChildNodeWithInfo(final DefaultMutableTreeNode rootNode, final CompilerOptionInfo info) { for (int i = 0; i < rootNode.getChildCount(); i++) { final DefaultMutableTreeNode childNode = (DefaultMutableTreeNode)rootNode.getChildAt(i); if (info.equals(childNode.getUserObject())) { return childNode; } } return null; } private boolean hasCustomValue(final CompilerOptionInfo info) { return myCurrentOptions.get(info.ID) != null; } @NotNull private Pair<String, ValueSource> getValueAndSource(final CompilerOptionInfo info) { if (info.isGroup()) { // choose "the most custom" subnode of a group a group source ValueSource groupValueSource = ValueSource.GlobalDefault; for (final CompilerOptionInfo childInfo : info.getChildOptionInfos()) { if (myMode != Mode.BC || childInfo.isApplicable(getSdkVersion(), myNature)) { final ValueSource childSource = getValueAndSource(childInfo).second; if (childSource.ordinal() > groupValueSource.ordinal()) { groupValueSource = childSource; } } } return Pair.create(null, groupValueSource); } final String customValue = myCurrentOptions.get(info.ID); if (customValue != null) { return Pair.create(customValue, ValueSource.BC); } if (myMode == Mode.BC) { final String moduleDefaultValue = myBCManager.getModuleLevelCompilerOptions().getOption(info.ID); if (moduleDefaultValue != null) { return Pair.create(moduleDefaultValue, ValueSource.ModuleDefault); } } if (myMode == Mode.BC || myMode == Mode.Module) { final String projectDefaultValue = myProjectLevelOptionsHolder.getProjectLevelCompilerOptions().getOption(info.ID); if (projectDefaultValue != null) { return Pair.create(projectDefaultValue, ValueSource.ProjectDefault); } } return Pair.create(info.getDefaultValue(getSdkVersion(), myNature, myDependenciesConfigurable.getCurrentComponentSet()), ValueSource.GlobalDefault); } private String getSdkVersion() { final Sdk sdk = myDependenciesConfigurable.getCurrentSdk(); final String sdkVersion = sdk == null ? null : sdk.getVersionString(); return sdkVersion == null ? UNKNOWN_SDK_VERSION : sdkVersion; } private class RepeatableValueEditor extends TextFieldWithBrowseButton { private final Project myProject; private CompilerOptionInfo myInfo; private String myValue; private String myAddedConditionalCompilerDefinition = null; private RepeatableValueEditor(final Project project) { myProject = project; getTextField().setEditable(false); setButtonIcon(PlatformIcons.OPEN_EDIT_DIALOG_ICON); addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent e) { final List<String> entries = StringUtil.split(myValue, CompilerOptionInfo.LIST_ENTRIES_SEPARATOR); final List<StringBuilder> buffers = new ArrayList<>(entries.size()); for (String entry : entries) { buffers.add(new StringBuilder(entry)); } Sdk sdk; if (myInfo.ID.equals("compiler.locale") && (sdk = myDependenciesConfigurable.getCurrentSdk()) != null && sdk.getSdkType() == FlexSdkType2.getInstance()) { final LocalesDialog dialog = new LocalesDialog(myProject, sdk, StringUtil.split(myValue, CompilerOptionInfo.LIST_ENTRIES_SEPARATOR)); if (dialog.showAndGet()) { myValue = StringUtil.join(dialog.getLocales(), CompilerOptionInfo.LIST_ENTRIES_SEPARATOR); } } else { final RepeatableValueDialog dialog = new RepeatableValueDialog(myProject, StringUtil.capitalizeWords(myInfo.DISPLAY_NAME, true), buffers, myInfo, myAddedConditionalCompilerDefinition); if (dialog.showAndGet()) { myValue = StringUtil.join(dialog.getCurrentList(), stringBuilder -> stringBuilder.toString(), CompilerOptionInfo.LIST_ENTRIES_SEPARATOR); } } TableUtil.stopEditing(myTreeTable); } }); } public void setInfoAndValue(final CompilerOptionInfo info, final String value) { myInfo = info; myValue = value; myAddedConditionalCompilerDefinition = null; setText(getPresentableSummary(value, info)); } public void setAutoAddConditionalCompilerDefinition(final String ccdName) { myAddedConditionalCompilerDefinition = ccdName; } public String getValue() { return myValue; } } static class ExtensionAwareFileChooserDescriptor extends FileChooserDescriptor { private @Nullable String[] myAllowedExtensions; public ExtensionAwareFileChooserDescriptor() { super(true, false, true, true, false, false); } public boolean isFileVisible(final VirtualFile file, final boolean showHiddenFiles) { return super.isFileVisible(file, showHiddenFiles) && (file.isDirectory() || isAllowedExtension(file.getExtension())); } private boolean isAllowedExtension(final String extension) { if (myAllowedExtensions == null) return true; for (String allowedExtension : myAllowedExtensions) { if (allowedExtension.equalsIgnoreCase(extension)) return true; } return false; } public void setAllowedExtensions(final @Nullable String... allowedExtensions) { myAllowedExtensions = allowedExtensions; } } private class RestoreDefaultValueAction extends AnAction { private final JTree myTree; public RestoreDefaultValueAction(final JTree tree) { super("Restore Default Value"); myTree = tree; } public void update(final AnActionEvent e) { TableUtil.stopEditing(myTreeTable); final CompilerOptionInfo info = getNodeAndInfo().second; e.getPresentation().setEnabled(info != null && hasCustomValue(info)); } public void actionPerformed(final AnActionEvent e) { final Pair<DefaultMutableTreeNode, CompilerOptionInfo> nodeAndInfo = getNodeAndInfo(); if (nodeAndInfo.second != null) { myMapModified = true; myCurrentOptions.remove(nodeAndInfo.second.ID); reloadNodeOrGroup(myTree, nodeAndInfo.first); } } private Pair<DefaultMutableTreeNode, CompilerOptionInfo> getNodeAndInfo() { final TreePath path = myTree.getSelectionPath(); final DefaultMutableTreeNode node = path == null ? null : (DefaultMutableTreeNode)path.getLastPathComponent(); final Object userObject = node == null ? null : node.getUserObject(); return userObject instanceof CompilerOptionInfo ? Pair.create(node, (CompilerOptionInfo)userObject) : Pair.empty(); } } public void setHistory(final History history) { } public ActionCallback navigateTo(@Nullable final Place place, final boolean requestFocus) { if (place != null) { final Object location = place.getPath(FlexBCConfigurable.LOCATION_ON_TAB); if (location instanceof Location) { switch ((Location)location) { case AdditionalConfigFile: return IdeFocusManager.findInstance().requestFocus(myConfigFileTextWithBrowse.getChildComponent(), true); case FilesToIncludeInSwc: return IdeFocusManager.findInstance().requestFocus(myIncludeInSWCField.getChildComponent(), true); case ConditionalCompilerDefinition: final DefaultMutableTreeNode root = (DefaultMutableTreeNode)myTreeTable.getTree().getModel().getRoot(); final CompilerOptionInfo info = CompilerOptionInfo.getOptionInfo("compiler.define"); final DefaultMutableTreeNode node = findChildNodeWithInfo(root, info); if (node != null) { myTreeTable.clearSelection(); myTreeTable.addSelectedPath(TreeUtil.getPath(root, node)); final Object ccdName = place.getPath(CONDITIONAL_COMPILER_DEFINITION_NAME); if (ccdName instanceof String) { TableUtil.editCellAt(myTreeTable, myTreeTable.getSelectedRow(), 1); final Component editor = myTreeTable.getEditorComponent(); if (editor instanceof RepeatableValueEditor) { ((RepeatableValueEditor)editor).setAutoAddConditionalCompilerDefinition((String)ccdName); ((RepeatableValueEditor)editor).getButton().doClick(); } } } break; } } } return ActionCallback.DONE; } public void queryPlace(@NotNull final Place place) { } }