com.android.tools.idea.structure.AndroidModuleStructureConfigurable.java Source code

Java tutorial

Introduction

Here is the source code for com.android.tools.idea.structure.AndroidModuleStructureConfigurable.java

Source

/*
 * Copyright (C) 2013 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.structure;

import com.android.tools.idea.gradle.parser.GradleSettingsFile;
import com.android.tools.idea.gradle.project.GradleProjectImporter;
import com.android.tools.idea.gradle.util.GradleUtil;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.options.ShowSettingsUtil;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectBundle;
import com.intellij.openapi.roots.ui.configuration.ModuleEditor;
import com.intellij.openapi.roots.ui.configuration.ProjectStructureConfigurable;
import com.intellij.openapi.roots.ui.configuration.projectRoot.BaseStructureConfigurable;
import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.ProjectStructureElement;
import com.intellij.openapi.ui.MasterDetailsComponent;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.ui.NamedConfigurable;
import com.intellij.openapi.util.ActionCallback;
import com.intellij.ui.navigation.Place;
import com.intellij.util.PlatformIcons;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import javax.swing.tree.DefaultTreeModel;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;

/**
 * A Project Structure {@linkplain com.intellij.openapi.options.Configurable} that lets users add/remove/configure modules.
 */
public class AndroidModuleStructureConfigurable extends BaseStructureConfigurable implements Place.Navigator {
    private static final Comparator<MyNode> NODE_COMPARATOR = new Comparator<MyNode>() {
        @Override
        public int compare(final MyNode o1, final MyNode o2) {
            return o1.getConfigurable().getDisplayName().compareToIgnoreCase(o2.getConfigurable().getDisplayName());
        }
    };

    @NonNls
    public static final String CATEGORY = "category";

    private final GradleSettingsFile mySettingsFile;

    public AndroidModuleStructureConfigurable(Project project) {
        super(project);
        mySettingsFile = GradleSettingsFile.get(project);
    }

    @Override
    protected String getComponentStateKey() {
        return "ModuleStructureConfigurable.UI";
    }

    @Override
    protected void initTree() {
        super.initTree();
    }

    @Override
    protected void loadTree() {
        if (mySettingsFile == null) {
            return;
        }
        // Our module names will be Gradle-style paths delimited by colons. We need to turn this into a tree structure, so for each
        // path we break it apart into leaves and turn the leaves into tree nodes if there isn't already a node in the tree for that leaf.
        // We walk these tree nodes so that we add the module to the correct parent node in the tree. We can be called to reload an existing
        // tree, so we have to be careful not to create any duplicates.
        for (final String path : mySettingsFile.getModules()) {
            MyNode parentNode = myRoot;
            List<String> segments = GradleUtil.getPathSegments(path);
            if (segments.isEmpty()) {
                continue;
            }
            String moduleName = segments.remove(segments.size() - 1);
            for (String segment : segments) {
                MyNode node = getNode(parentNode, segment);
                if (node == null) {
                    node = new MyNode(new FolderConfigurable(segment));
                    addNode(node, parentNode);
                }
                parentNode = node;
            }
            if (getNode(parentNode, moduleName) == null) {
                final MyNode moduleNode = new MyNode(new AndroidModuleConfigurable(myProject, path));
                addNode(moduleNode, parentNode);
            }
        }
        myTree.setShowsRootHandles(true);
        ((DefaultTreeModel) myTree.getModel()).reload();
        myUiDisposed = false;
    }

    @Nullable
    private static MyNode getNode(MyNode parentNode, String leaf) {
        for (int i = 0; i < parentNode.getChildCount(); i++) {
            MyNode child = (MyNode) parentNode.getChildAt(i);
            if (child.getDisplayName().equals(leaf)) {
                return child;
            }
        }
        return null;
    }

    @NotNull
    @Override
    protected Collection<? extends ProjectStructureElement> getProjectStructureElements() {
        return ImmutableList.of();
    }

    @Override
    protected Comparator<MyNode> getNodeComparator() {
        return NODE_COMPARATOR;
    }

    @Override
    public void disposeUIResources() {
        super.disposeUIResources();
        myContext.myModulesConfigurator.disposeUIResources();
    }

    @Override
    public void dispose() {
    }

    @Override
    protected void processRemovedItems() {
    }

    @Override
    protected boolean wasObjectStored(Object editableObject) {
        return false;
    }

    @Override
    public String getDisplayName() {
        return ProjectBundle.message("project.roots.display.name");
    }

    @Override
    @Nullable
    @NonNls
    public String getHelpTopic() {
        return null;
    }

    @Override
    @NotNull
    protected ArrayList<AnAction> createActions(final boolean fromPopup) {
        final ArrayList<AnAction> result = new ArrayList<AnAction>(2);
        result.add(createAddAction());
        result.add(new MyRemoveAction());
        return result;
    }

    @Override
    protected boolean canBeRemoved(Object[] editableObjects) {
        for (Object editableObject : editableObjects) {
            if (editableObject != null && editableObject instanceof String) {
                String moduleName = (String) editableObject;
                if (Iterables.contains(mySettingsFile.getModules(), moduleName)) {
                    return true;
                }
            }
        }
        return false;
    }

    @Override
    protected boolean removeObject(Object editableObject) {
        if (editableObject != null && editableObject instanceof String) {
            final String moduleName = (String) editableObject;
            if (Iterables.contains(mySettingsFile.getModules(), moduleName)) {

                // TODO: This applies changes immediately. We need to save up changes and not apply them until the user confirms the dialog.
                // In the old dialog this is handled by the fact that we have a ModifiableRootModel that queues changes.

                // TODO: If removing a module, remove any dependencies on that module in other modules. Perhaps we should show this to the user
                // in the confirmation dialog, and ask if dependencies should be cleaned up?

                String question;
                if (Iterables.size(mySettingsFile.getModules()) == 1) {
                    question = ProjectBundle.message("module.remove.last.confirmation");
                } else {
                    question = ProjectBundle.message("module.remove.confirmation", moduleName);
                }
                int result = Messages.showYesNoDialog(myProject, question,
                        ProjectBundle.message("module.remove.confirmation.title"), Messages.getQuestionIcon());
                if (result != 0) {
                    return false;
                }
                ApplicationManager.getApplication().runWriteAction(new Runnable() {
                    @Override
                    public void run() {
                        mySettingsFile.removeModule(moduleName);
                    }
                });
                return true;
            }
        }
        return false;
    }

    public static AndroidModuleStructureConfigurable getInstance(final Project project) {
        return ServiceManager.getService(project, AndroidModuleStructureConfigurable.class);
    }

    /**
     * Opens a Project Settings dialog and selects the Gradle module editor, with the given module and editor pane active.
     */
    public static boolean showDialog(final Project project, @Nullable final String moduleToSelect,
            @Nullable final String editorToSelect) {
        final ProjectStructureConfigurable config = ProjectStructureConfigurable.getInstance(project);
        return ShowSettingsUtil.getInstance().editConfigurable(project, config, new Runnable() {
            @Override
            public void run() {
                getInstance(project).select(moduleToSelect, editorToSelect, true);
            }
        });
    }

    private ActionCallback select(@Nullable final String moduleToSelect, @Nullable String editorNameToSelect,
            final boolean requestFocus) {
        Place place = new Place().putPath(CATEGORY, this);
        if (moduleToSelect != null) {
            final Module module = ModuleManager.getInstance(myProject).findModuleByName(moduleToSelect);
            assert module != null;
            place = place.putPath(MasterDetailsComponent.TREE_OBJECT, module)
                    .putPath(ModuleEditor.SELECTED_EDITOR_NAME, editorNameToSelect);
        }
        return navigateTo(place, requestFocus);
    }

    private void addModule() {
        myContext.myModulesConfigurator.addModule(myTree, false);

        // Template instantiation is already being run via invokeLater. When it's done, pick up the changes to settings.gradle and reload
        // the tree.
        ApplicationManager.getApplication().invokeLater(new Runnable() {
            @Override
            public void run() {
                ApplicationManager.getApplication().runWriteAction(new Runnable() {
                    @Override
                    public void run() {
                        mySettingsFile.reload();
                        loadTree();
                    }
                });
            }
        });
    }

    @Override
    @NotNull
    @NonNls
    public String getId() {
        return "project.structure";
    }

    @Override
    @Nullable
    public Runnable enableSearch(String option) {
        return null;
    }

    @Override
    protected AbstractAddGroup createAddAction() {
        return new AbstractAddGroup(ProjectBundle.message("add.new.header.text")) {
            @Override
            @NotNull
            public AnAction[] getChildren(@Nullable final AnActionEvent e) {
                AnAction addModuleAction = new AddModuleAction();
                addModuleAction.getTemplatePresentation().setText("New Module");
                return new AnAction[] { addModuleAction };
            }
        };
    }

    @Override
    @Nullable
    protected String getEmptySelectionString() {
        return ProjectBundle.message("empty.module.selection.string");
    }

    @Override
    public void apply() throws ConfigurationException {
        super.apply();
        try {
            GradleProjectImporter.getInstance().reImportProject(myProject, null);
        } catch (ConfigurationException ex) {
            Messages.showErrorDialog(ex.getMessage(), ex.getTitle());
            LOG.info(ex);
        }
    }

    private class AddModuleAction extends AnAction implements DumbAware {
        public AddModuleAction() {
            super(ProjectBundle.message("add.new.module.text.full"), null, AllIcons.Actions.Module);
        }

        @Override
        public void actionPerformed(final AnActionEvent e) {
            addModule();
        }
    }

    private static class FolderConfigurable extends NamedConfigurable {
        private final String myName;

        public FolderConfigurable(String name) {
            myName = name;
        }

        @Override
        public void setDisplayName(String name) {
        }

        @Override
        @Nullable
        public Object getEditableObject() {
            return null;
        }

        @Override
        public String getBannerSlogan() {
            return "Folder '" + myName + "'";
        }

        @Override
        public JComponent createOptionsPanel() {
            return new JPanel();
        }

        @Nls
        @Override
        public String getDisplayName() {
            return myName;
        }

        @Nullable
        @Override
        public String getHelpTopic() {
            return null;
        }

        @Override
        public boolean isModified() {
            return false;
        }

        @Override
        public void apply() throws ConfigurationException {
        }

        @Override
        public void reset() {
        }

        @Override
        public void disposeUIResources() {
        }

        @Nullable
        @Override
        public Icon getIcon(boolean expanded) {
            return PlatformIcons.FOLDER_ICON;
        }
    }
}