blob: f06c1338082510e9fe585d265513e3abdd5611d0 [file] [log] [blame]
/*
* 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);
}
}
}
}
}