/* * Copyright (C) 2014 The Android Open Source Project * * 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.android.tools.idea.gradle.variant.conflict; import com.android.tools.idea.gradle.util.ModuleTypeComparator; import com.android.tools.idea.gradle.variant.ui.VariantCheckboxTreeCellRenderer; import com.google.common.collect.Lists; import com.intellij.openapi.module.Module; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.ui.ValidationInfo; import com.intellij.openapi.util.text.StringUtil; import com.intellij.ui.CheckboxTree; import com.intellij.ui.CheckedTreeNode; import com.intellij.util.ui.tree.TreeUtil; import org.jdesktop.swingx.JXLabel; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; import java.awt.*; import java.util.Collections; import java.util.Enumeration; import java.util.List; import static com.android.tools.idea.gradle.util.ui.ToolWindowAlikePanel.createTreePanel; /** * Displays the variants of a module and their dependents. The purpose of this dialog is to help users decide the build variant to choose * when a "variant selection" conflict cannot be automatically solved. * <p> * A conflict cannot be automatically solved when multiple modules depend on more than one variant of another module. For example: * <ul> * <li>Module A depends on variant X in module C</li> * <li>Module B depends on variant Y om module C</li> * <li>Module C has variant Z selected in the "Build Variants" window</li> * </ul> * It is not possible to solve this conflict without creating a new one: if we select variant X, there will be a conflict with module B and * if we select variant Y, there will be a conflict with module A. * </p> */ class ConflictResolutionDialog extends DialogWrapper { private final JPanel myPanel; private final ConflictTree myTree; ConflictResolutionDialog(@NotNull Conflict conflict) { super(conflict.getSource().getProject()); setTitle("Resolve Variant Selection Conflict"); myPanel = new JPanel(new BorderLayout()); myPanel.setPreferredSize(new Dimension(400, 400)); init(); VariantCheckboxTreeCellRenderer renderer = new VariantCheckboxTreeCellRenderer() { @Override public void customizeRenderer(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { if (value instanceof DefaultMutableTreeNode) { Object data = ((DefaultMutableTreeNode)value).getUserObject(); if (data instanceof String) { appendVariant((String)data); } if (data instanceof Module) { appendModule((Module)data, null); } } } }; //noinspection ConstantConditions CheckedTreeNode root = new CheckedTreeNode(null); myTree = new ConflictTree(renderer, root); myTree.setRootVisible(false); List<String> variants = Lists.newArrayList(conflict.getVariants()); Collections.sort(variants); for (String variant : variants) { CheckedTreeNode variantNode = new CheckedTreeNode(variant); variantNode.setChecked(false); root.add(variantNode); List<Module> dependents = Lists.newArrayList(); for (Conflict.AffectedModule affected : conflict.getModulesExpectingVariant(variant)) { Module module = affected.getTarget(); dependents.add(module); } if (dependents.size() > 1) { Collections.sort(dependents, ModuleTypeComparator.INSTANCE); } for (Module dependent : dependents) { DefaultMutableTreeNode moduleNode = new DefaultMutableTreeNode(dependent); variantNode.add(moduleNode); } } myTree.expandAll(); JXLabel descriptionLabel = new JXLabel(); descriptionLabel.setLineWrap(true); String sourceName = conflict.getSource().getName(); String text = "The conflict cannot be automatically solved.\n"; text += String.format("Module '%1$s' has variant '%2$s' selected, but multiple modules require different variants.", sourceName, conflict.getSelectedVariant()); descriptionLabel.setText(text); // Leave some space between the description and the tree. descriptionLabel.setBorder(BorderFactory.createEmptyBorder(2, 2, 5, 2)); myPanel.add(descriptionLabel, BorderLayout.NORTH); String title = String.format("Variants in '%1$s' and their dependents", sourceName); myPanel.add(createTreePanel(title, myTree), BorderLayout.CENTER); } @Override @NotNull protected JComponent createCenterPanel() { return myPanel; } @Override @NotNull protected Action[] createActions() { return super.createActions(); } @Override protected boolean postponeValidation() { return false; } @Override @Nullable protected ValidationInfo doValidate() { if (StringUtil.isEmpty(getSelectedVariant())) { return new ValidationInfo("Please choose the variant to set"); } return null; } @Nullable String getSelectedVariant() { return myTree.getSelectedVariant(); } private static class ConflictTree extends CheckboxTree { @NotNull final CheckedTreeNode myRoot; ConflictTree(@NotNull CheckboxTreeCellRenderer cellRenderer, @NotNull CheckedTreeNode root) { super(cellRenderer, root); myRoot = root; } @Nullable String getSelectedVariant() { Enumeration moduleNodes = myRoot.children(); while (moduleNodes.hasMoreElements()) { Object child = moduleNodes.nextElement(); if (child instanceof CheckedTreeNode) { CheckedTreeNode node = (CheckedTreeNode)child; if (node.isChecked()) { return node.getUserObject().toString(); } } } return null; } void expandAll() { TreeUtil.expandAll(this); } @Override public DefaultTreeModel getModel() { return (DefaultTreeModel)super.getModel(); } @Override protected void onNodeStateChanged(CheckedTreeNode node) { if (!node.isChecked()) { return; } Enumeration moduleNodes = myRoot.children(); while (moduleNodes.hasMoreElements()) { Object child = moduleNodes.nextElement(); if (child != node && child instanceof CheckedTreeNode) { CheckedTreeNode childNode = (CheckedTreeNode)child; childNode.setChecked(false); } } } } }