/* * Copyright 2000-2015 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 com.intellij.psi.codeStyle.extractor.ui; import com.intellij.openapi.application.ApplicationBundle; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.psi.codeStyle.presentation.CodeStyleSettingPresentation; import com.intellij.psi.codeStyle.LanguageCodeStyleSettingsProvider; import com.intellij.psi.codeStyle.extractor.values.Value; import com.intellij.ui.SpeedSearchComparator; import com.intellij.ui.TreeTableSpeedSearch; import com.intellij.ui.components.JBScrollPane; import com.intellij.ui.components.panels.HorizontalLayout; import com.intellij.ui.treeStructure.treetable.*; 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 org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.table.TableCellEditor; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; import javax.swing.tree.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.*; import java.util.List; /** * @author Roman.Shein * @since 28.09.2015. */ public class ExtractedSettingsDialog extends DialogWrapper { protected CodeStyleSettingsNameProvider myNameProvider; protected List<Value> myValues; protected DefaultMutableTreeNode myRoot; public ExtractedSettingsDialog(@Nullable Project project, @NotNull CodeStyleSettingsNameProvider nameProvider, @NotNull List<Value> values) { super(project, false); myNameProvider = nameProvider; myValues = values; setModal(true); init(); setTitle("Extracted Code Style Settings"); } @Nullable @Override protected JComponent createCenterPanel() { JComponent result = buildExtractedSettingsTree(); return result; } public boolean valueIsSelectedInTree(@NotNull Value value) { if (myRoot == null) return false; return valueIsSelectedInTree(myRoot, value); } protected boolean valueIsSelectedInTree(@NotNull TreeNode startNode, @NotNull Value value) { for (Enumeration children = startNode.children(); children.hasMoreElements();) { Object child = children.nextElement(); if (child instanceof SettingsTreeNode) { SettingsTreeNode settingsChild = (SettingsTreeNode) child; if (settingsChild.accepted && value.equals(settingsChild.myValue)) { return true; } if (valueIsSelectedInTree(settingsChild, value)) return true; } else if (child instanceof TreeNode) { if (valueIsSelectedInTree((TreeNode) child, value)) return true; } } return false; } public static class SettingsTreeNode extends DefaultMutableTreeNode { protected CodeStyleSettingPresentation myRepresentation; protected boolean accepted = true; protected final String valueString; protected final boolean isGroupNode; protected final String customTitle; protected Value myValue; public SettingsTreeNode(String valueString, CodeStyleSettingPresentation representation, boolean isGroupNode, Value value) { this(valueString, representation, isGroupNode, null, value); } public SettingsTreeNode(String valueString, CodeStyleSettingPresentation representation, boolean isGroupNode, String customTitle, Value value) { this.valueString = valueString; this.myRepresentation = representation; this.isGroupNode = isGroupNode; this.customTitle = customTitle; this.myValue = value; } public SettingsTreeNode(String title) { this(title, null, true, null); } public boolean isGroupOrTypeNode() { return isGroupNode; } @NotNull public String getTitle() { return customTitle != null ? customTitle : (myRepresentation == null ? valueString : myRepresentation.getUiName()); } @Nullable public String getValueString() { return myRepresentation == null ? null : valueString; } } protected static ColumnInfo getTitleColumnInfo() { return new ColumnInfo("TITLE") { @Nullable @Override public Object valueOf(Object o) { if (o instanceof SettingsTreeNode) { return ((SettingsTreeNode) o).getTitle(); } else { return o.toString(); } } @Override public Class getColumnClass() { return TreeTableModel.class; } }; } protected static class ValueRenderer implements TableCellRenderer { private final JLabel myLabel = new JLabel(); private final JCheckBox myCheckBox = new JCheckBox(); private final JPanel myPanel = new JPanel(new HorizontalLayout(0)); { myPanel.add(myLabel); myPanel.add(myCheckBox); } @NotNull @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { if (table instanceof TreeTable) { table.setEnabled(true); DefaultMutableTreeNode valueNode = (DefaultMutableTreeNode)((TreeTable) table).getTree().getPathForRow(row).getLastPathComponent(); if (valueNode instanceof SettingsTreeNode) { SettingsTreeNode settingsNode = (SettingsTreeNode) valueNode; myLabel.setText(settingsNode.getValueString()); myCheckBox.setEnabled(true); myCheckBox.setSelected(settingsNode.accepted); } else { myLabel.setBackground(table.getBackground()); myCheckBox.setEnabled(false); } } return myPanel; } } protected static class ValueEditor extends AbstractTableCellEditor { private final JLabel myLabel = new JLabel(); private final JCheckBox myCheckBox = new JCheckBox(); private final JPanel myPanel = new JPanel(new HorizontalLayout(0)); { myPanel.add(myLabel); myPanel.add(myCheckBox); } private SettingsTreeNode myCurrentNode; private TreeTable myCurrentTree; final ActionListener itemChoiceListener = new ActionListener() { @Override public void actionPerformed(@NotNull ActionEvent e) { if (myCurrentNode != null) { boolean wasChanged = myCurrentNode.accepted != myCheckBox.isSelected(); myCurrentNode.accepted = myCheckBox.isSelected(); if (wasChanged) { updateAncestorsUi(myCurrentNode.accepted, myCurrentNode); updateChildrenUi(myCurrentNode); } if (myCurrentTree != null) { myCurrentTree.repaint(); } } } }; protected void updateAncestorsUi(boolean accepted, SettingsTreeNode node) { TreeNode parent = node.getParent(); if (parent != null && parent instanceof SettingsTreeNode) { SettingsTreeNode settingsParent = (SettingsTreeNode) parent; settingsParent.accepted = false; if (!accepted) { //propagate disabled settings upwards updateAncestorsUi(false, settingsParent); } else { for (Enumeration children = parent.children(); children.hasMoreElements(); ) { Object child = children.nextElement(); if ((child instanceof SettingsTreeNode) && !((SettingsTreeNode) child).accepted) return; } settingsParent.accepted = true; updateAncestorsUi(true, settingsParent); } } } protected void updateChildrenUi(SettingsTreeNode node) { for (Enumeration children = node.children(); children.hasMoreElements(); ) { Object child = children.nextElement(); if (child instanceof SettingsTreeNode) { SettingsTreeNode settingsChild = (SettingsTreeNode) child; settingsChild.accepted = node.accepted; updateChildrenUi(settingsChild); } } } public ValueEditor() { myCheckBox.addActionListener(itemChoiceListener); } @Override public Object getCellEditorValue() { return myCheckBox.isSelected(); } @Override public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode) ((TreeTable) table).getTree().getPathForRow(row).getLastPathComponent(); if (treeNode instanceof SettingsTreeNode) { myCurrentTree = (TreeTable) table; myCurrentNode = (SettingsTreeNode) treeNode; myLabel.setText(myCurrentNode.getValueString()); myCheckBox.setSelected(myCurrentNode.accepted); } return myPanel; } } private static ValueRenderer myValueRenderer = new ValueRenderer(); private static ValueEditor myValueEditor = new ValueEditor(); protected static ColumnInfo getValueColumnInfo() { return new ColumnInfo("VALUE") { @Nullable @Override public Object valueOf(Object o) { if (o instanceof SettingsTreeNode) { return ((SettingsTreeNode) o).getValueString(); } else { return null; } } @Override public TableCellRenderer getRenderer(Object o) { return myValueRenderer; } public TableCellEditor getEditor(Object o) { return myValueEditor; } @Override public boolean isCellEditable(Object o) { return o instanceof SettingsTreeNode; } }; } protected JComponent buildExtractedSettingsTree() { Collection<Value> unusedValues = ContainerUtil.newHashSet(myValues); myRoot = new DefaultMutableTreeNode(); for (Map.Entry<LanguageCodeStyleSettingsProvider.SettingsType, Map<CodeStyleSettingPresentation.SettingsGroup, List<CodeStyleSettingPresentation>>> typeEntry : myNameProvider.mySettings.entrySet()) { DefaultMutableTreeNode settingsNode = null; for (Map.Entry<CodeStyleSettingPresentation.SettingsGroup, List<CodeStyleSettingPresentation>> groupEntry: typeEntry.getValue().entrySet()) { CodeStyleSettingPresentation.SettingsGroup group = groupEntry.getKey(); List<CodeStyleSettingPresentation> representations = groupEntry.getValue(); List<CodeStyleSettingPresentation> children = ContainerUtil.emptyList(); DefaultMutableTreeNode groupNode = null; if (group.name == null && !representations.isEmpty()) { //there is a setting with name coinciding with group name if (representations.size() > 1) { children = representations.subList(1, representations.size()); } CodeStyleSettingPresentation headRep = representations.get(0); Value myValue = CodeStyleSettingsNameProvider.getValue(headRep, myValues); if (myValue == null) { //value was not found (was not selected) groupNode = new SettingsTreeNode(headRep.getUiName()); } else { groupNode = new SettingsTreeNode(headRep.getUiName()); groupNode.add(new SettingsTreeNode(headRep.getValueUiName(myValue.value), headRep, true, myValue)); unusedValues.remove(myValue); } } else { children = representations; } for (CodeStyleSettingPresentation representation: children) { Value myValue = CodeStyleSettingsNameProvider.getValue(representation, myValues); if (myValue != null) { if (groupNode == null) { groupNode = new SettingsTreeNode(group.name); } groupNode.add(new SettingsTreeNode(representation.getValueUiName(myValue.value), representation, false, myValue)); unusedValues.remove(myValue); } } if (groupNode != null && !groupNode.isLeaf()) { if (settingsNode == null) { settingsNode = new SettingsTreeNode(CodeStyleSettingsNameProvider.getSettingsTypeName(typeEntry.getKey())); } settingsNode.add(groupNode); } } if (settingsNode != null) { myRoot.add(settingsNode); } } //TODO: for now, settings without UI presentation are not displayed. Do something about it. //unusedValues = ContainerUtil.filter(unusedValues, new Condition<Value>(){ // @Override // public boolean value(Value value) { // return value.state == Value.STATE.SELECTED; // } //}); // //DefaultMutableTreeNode unnamedNode = null; //for (Value value: unusedValues) { // if (unnamedNode == null) { // unnamedNode = new SettingsTreeNode("Settings without UI representation"); // } // unnamedNode.add(new SettingsTreeNode(value.value.toString(), null, false, value.name, value)); //} // //if (unnamedNode != null) { // myRoot.add(unnamedNode); //} final ColumnInfo[] COLUMNS = new ColumnInfo[]{getTitleColumnInfo(), getValueColumnInfo()}; ListTreeTableModel model = new ListTreeTableModel(myRoot, COLUMNS); final TreeTable treeTable = new TreeTable(model) { @Override public TreeTableCellRenderer createTableRenderer(TreeTableModel treeTableModel) { TreeTableCellRenderer tableRenderer = super.createTableRenderer(treeTableModel); UIUtil.setLineStyleAngled(tableRenderer); tableRenderer.setRootVisible(false); tableRenderer.setShowsRootHandles(true); return tableRenderer; } @Override public TableCellRenderer getCellRenderer(int row, int column) { TreePath treePath = getTree().getPathForRow(row); if (treePath == null) return super.getCellRenderer(row, column); Object node = treePath.getLastPathComponent(); TableCellRenderer renderer = COLUMNS[column].getRenderer(node); return renderer == null ? super.getCellRenderer(row, column) : renderer; } @Override public TableCellEditor getCellEditor(int row, int column) { TreePath treePath = getTree().getPathForRow(row); if (treePath == null) return super.getCellEditor(row, column); Object node = treePath.getLastPathComponent(); TableCellEditor editor = COLUMNS[column].getEditor(node); return editor == null ? super.getCellEditor(row, column) : editor; } }; new TreeTableSpeedSearch(treeTable).setComparator(new SpeedSearchComparator(false)); treeTable.setRootVisible(false); final JTree tree = treeTable.getTree(); tree.setCellRenderer(myTitleRenderer); tree.setShowsRootHandles(true); treeTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); treeTable.setTableHeader(null); TreeUtil.expandAll(tree); treeTable.getColumnModel().getSelectionModel().setAnchorSelectionIndex(1); treeTable.getColumnModel().getSelectionModel().setLeadSelectionIndex(1); int maxWidth = tree.getPreferredScrollableViewportSize().width + 10; final TableColumn titleColumn = treeTable.getColumnModel().getColumn(0); titleColumn.setPreferredWidth(maxWidth); titleColumn.setMinWidth(maxWidth); titleColumn.setMaxWidth(maxWidth); titleColumn.setResizable(false); final Dimension valueSize = new JLabel(ApplicationBundle.message("option.table.sizing.text")).getPreferredSize(); treeTable.setPreferredScrollableViewportSize(new Dimension(maxWidth + valueSize.width + 10, 20)); treeTable.setBackground(UIUtil.getPanelBackground()); treeTable.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 10)); final Dimension screenSize = treeTable.getToolkit().getScreenSize(); JBScrollPane scroller = new JBScrollPane(treeTable) { @Override public Dimension getMinimumSize() { return super.getPreferredSize(); } }; final Dimension preferredSize = new Dimension(Math.min(screenSize.width / 2, treeTable.getPreferredSize().width), Math.min(screenSize.height / 2, treeTable.getPreferredSize().height)); getRootPane().setPreferredSize(preferredSize); return scroller; } final TreeCellRenderer myTitleRenderer = new CellRenderer(); public static class CellRenderer implements TreeCellRenderer { private final JLabel myLabel = new JLabel(); @NotNull @Override public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { if (value instanceof SettingsTreeNode) { SettingsTreeNode node = (SettingsTreeNode) value; myLabel.setText(node.getTitle()); myLabel.setFont(node.isGroupOrTypeNode() ? myLabel.getFont().deriveFont(Font.BOLD) : myLabel.getFont().deriveFont(Font.PLAIN)); } else { myLabel.setText(value.toString()); myLabel.setFont(myLabel.getFont().deriveFont(Font.BOLD)); } Color foreground = selected ? UIUtil.getTableSelectionForeground() : UIUtil.getTableForeground(); myLabel.setForeground(foreground); return myLabel; } } }