All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.android.tools.idea.structure.ModuleDependenciesPanel Maven / Gradle / Ivy

/*
 * 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.ide.common.repository.GradleCoordinate;
import com.android.tools.idea.gradle.parser.*;
import com.android.tools.idea.gradle.util.GradleUtil;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.intellij.icons.AllIcons;
import com.intellij.ide.util.ChooseElementsDialog;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.ActionPlaces;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileChooser.FileChooser;
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectBundle;
import com.intellij.openapi.roots.ui.CellAppearanceEx;
import com.intellij.openapi.roots.ui.util.SimpleTextCellAppearance;
import com.intellij.openapi.ui.ComboBox;
import com.intellij.openapi.ui.ComboBoxTableRenderer;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.popup.JBPopup;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.ui.popup.PopupStep;
import com.intellij.openapi.ui.popup.util.BaseListPopupStep;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.*;
import com.intellij.ui.table.JBTable;
import com.intellij.util.ActionRunner;
import com.intellij.util.PlatformIcons;
import icons.AndroidIcons;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.TableColumn;
import javax.swing.table.TableRowSorter;
import java.awt.*;
import java.util.EnumSet;
import java.util.List;

/**
 * A GUI object that displays and modifies dependencies for an Android-Gradle module.
 */
public class ModuleDependenciesPanel extends EditorPanel {
  private static final Logger LOG = Logger.getInstance(ModuleDependenciesPanel.class);
  private static final int SCOPE_COLUMN_WIDTH = 120;
  private final JBTable myEntryTable;
  private final ModuleDependenciesTableModel myModel;
  private final String myModulePath;
  private final Project myProject;
  private final GradleBuildFile myGradleBuildFile;
  private final GradleSettingsFile myGradleSettingsFile;
  private AnActionButton myRemoveButton;

  public ModuleDependenciesPanel(@NotNull Project project, @NotNull String modulePath) {
    super(new BorderLayout());

    myModulePath = modulePath;
    myProject = project;
    myModel = new ModuleDependenciesTableModel();
    myGradleSettingsFile = GradleSettingsFile.get(myProject);

    Module module = GradleUtil.findModuleByGradlePath(myProject, modulePath);
    myGradleBuildFile = module != null ? GradleBuildFile.get(module) : null;
    if (myGradleBuildFile != null) {
      List dependencies = myGradleBuildFile.getDependencies();
      for (BuildFileStatement dependency : dependencies) {
        myModel.addItem(new ModuleDependenciesTableItem(dependency));
      }
    } else {
      LOG.warn("Unable to find Gradle build file for module " + myModulePath);
    }
    myModel.resetModified();

    myEntryTable = new JBTable(myModel);
    TableRowSorter sorter = new TableRowSorter(myModel);
    sorter.setRowFilter(myModel.getFilter());
    myEntryTable.setRowSorter(sorter);
    myEntryTable.setShowGrid(false);
    myEntryTable.setDragEnabled(false);
    myEntryTable.setIntercellSpacing(new Dimension(0, 0));

    myEntryTable.setDefaultRenderer(ModuleDependenciesTableItem.class, new TableItemRenderer());

    if (myGradleBuildFile == null) {
      return;
    }
    final boolean isAndroid = myGradleBuildFile.hasAndroidPlugin();
    List scopes = Lists.newArrayList(
      Sets.filter(EnumSet.allOf(Dependency.Scope.class), new Predicate() {
        @Override
        public boolean apply(Dependency.Scope input) {
          return isAndroid ? input.isAndroidScope() : input.isJavaScope();
        }
      }));
    ComboBoxModel boxModel = new CollectionComboBoxModel(scopes, null);
    JComboBox scopeEditor = new ComboBox(boxModel);
    myEntryTable.setDefaultEditor(Dependency.Scope.class, new DefaultCellEditor(scopeEditor));
    myEntryTable.setDefaultRenderer(Dependency.Scope.class, new ComboBoxTableRenderer(Dependency.Scope.values()) {
        @Override
        protected String getTextFor(@NotNull final Dependency.Scope value) {
          return value.getDisplayName();
        }
      });

    myEntryTable.getSelectionModel().setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);

    new SpeedSearchBase(myEntryTable) {
      @Override
      public int getSelectedIndex() {
        return myEntryTable.getSelectedRow();
      }

      @Override
      protected int convertIndexToModel(int viewIndex) {
        return myEntryTable.convertRowIndexToModel(viewIndex);
      }

      @Override
      @NotNull
      public Object[] getAllElements() {
        return myModel.getItems().toArray();
      }

      @Override
      @NotNull
      public String getElementText(Object element) {
        return getCellAppearance((ModuleDependenciesTableItem)element).getText();
      }

      @Override
      public void selectElement(@NotNull Object element, @NotNull String selectedText) {
        final int count = myModel.getRowCount();
        for (int row = 0; row < count; row++) {
          if (element.equals(myModel.getItemAt(row))) {
            final int viewRow = myEntryTable.convertRowIndexToView(row);
            myEntryTable.getSelectionModel().setSelectionInterval(viewRow, viewRow);
            TableUtil.scrollSelectionToVisible(myEntryTable);
            break;
          }
        }
      }
    };

    TableColumn column = myEntryTable.getTableHeader().getColumnModel().getColumn(ModuleDependenciesTableModel.SCOPE_COLUMN);
    column.setResizable(false);
    column.setMaxWidth(SCOPE_COLUMN_WIDTH);
    column.setMinWidth(SCOPE_COLUMN_WIDTH);

    add(createTableWithButtons(), BorderLayout.CENTER);

    if (myEntryTable.getRowCount() > 0) {
      myEntryTable.getSelectionModel().setSelectionInterval(0, 0);
    }

    DefaultActionGroup actionGroup = new DefaultActionGroup();
    actionGroup.add(myRemoveButton);
    PopupHandler.installPopupHandler(myEntryTable, actionGroup, ActionPlaces.UNKNOWN, ActionManager.getInstance());
  }

  @NotNull
  private JComponent createTableWithButtons() {
    myEntryTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
      @Override
      public void valueChanged(ListSelectionEvent e) {
        if (e.getValueIsAdjusting()) {
          return;
        }
        updateButtons();
      }
    });

    final ToolbarDecorator decorator = ToolbarDecorator.createDecorator(myEntryTable);
    decorator.setAddAction(new AnActionButtonRunnable() {
      @Override
      public void run(AnActionButton button) {
        ImmutableList popupActions = ImmutableList.of(
          new PopupAction(AndroidIcons.MavenLogo, 1, "Library dependency") {
            @Override
            public void run() {
              addExternalDependency();
            }
          }, new PopupAction(PlatformIcons.LIBRARY_ICON, 2, "File dependency") {
            @Override
            public void run() {
              addFileDependency();
            }
          }, new PopupAction(AllIcons.Nodes.Module, 3, "Module dependency") {
            @Override
            public void run() {
              addModuleDependency();
            }
          }
        );
        final JBPopup popup = JBPopupFactory.getInstance().createListPopup(new BaseListPopupStep(null, popupActions) {
          @Override
          public Icon getIconFor(PopupAction value) {
            return value.myIcon;
          }

          @Override
          public boolean hasSubstep(PopupAction value) {
            return false;
          }

          @Override
          public boolean isMnemonicsNavigationEnabled() {
            return true;
          }

          @Override
          public PopupStep onChosen(final PopupAction value, final boolean finalChoice) {
            return doFinalStep(new Runnable() {
              @Override
              public void run() {
                value.run();
              }
            });
          }

          @Override
          @NotNull
          public String getTextFor(PopupAction value) {
            return "&" + value.myIndex + "  " + value.myTitle;
          }
        });
        popup.show(button.getPreferredPopupPoint());
      }
    });
    decorator.setRemoveAction(new AnActionButtonRunnable() {
        @Override
        public void run(AnActionButton button) {
          removeSelectedItems();
        }
      });
    decorator.setMoveUpAction(new AnActionButtonRunnable() {
      @Override
      public void run(AnActionButton button) {
        moveSelectedRows(-1);
      }
    });
    decorator.setMoveDownAction(new AnActionButtonRunnable() {
        @Override
        public void run(AnActionButton button) {
          moveSelectedRows(+1);
        }
      });

    final JPanel panel = decorator.createPanel();
    myRemoveButton = ToolbarDecorator.findRemoveButton(panel);
    return panel;
  }

  private void addExternalDependency() {
    Module module = GradleUtil.findModuleByGradlePath(myProject, myModulePath);
    MavenDependencyLookupDialog dialog = new MavenDependencyLookupDialog(myProject, module);
    dialog.setTitle("Choose Library Dependency");
    dialog.show();
    if (dialog.getExitCode() == DialogWrapper.OK_EXIT_CODE) {
      String coordinateText = dialog.getSearchText();
      myModel.addItem(new ModuleDependenciesTableItem(
          new Dependency(Dependency.Scope.COMPILE, Dependency.Type.EXTERNAL, coordinateText)));
    }
    myModel.fireTableDataChanged();
  }

  private void addFileDependency() {
    FileChooserDescriptor descriptor = new FileChooserDescriptor(false, false, true, true, false, false);
    VirtualFile buildFile = myGradleBuildFile.getFile();
    VirtualFile parent = buildFile.getParent();
    descriptor.setRoots(parent);
    VirtualFile virtualFile = FileChooser.chooseFile(descriptor, myProject, null);
    if (virtualFile != null) {
      String path = VfsUtilCore.getRelativePath(virtualFile, parent, '/');
      if (path == null) {
        path = virtualFile.getPath();
      }
      myModel.addItem(new ModuleDependenciesTableItem(new Dependency(Dependency.Scope.COMPILE, Dependency.Type.FILES, path)));
    }
    myModel.fireTableDataChanged();
  }

  private void addModuleDependency() {
    List modules = Lists.newArrayList();
    for (String s : myGradleSettingsFile.getModules()) {
      modules.add(s);
    }
    List dependencies = myGradleBuildFile.getDependencies();
    for (BuildFileStatement dependency : dependencies) {
      if (dependency instanceof Dependency) {
        Object data = ((Dependency)dependency).data;
        if (data instanceof String) {
          modules.remove(data);
        }
      }
    }
    modules.remove(myModulePath);
    final Component parent = this;
    final String title = ProjectBundle.message("classpath.chooser.title.add.module.dependency");
    final String description = ProjectBundle.message("classpath.chooser.description.add.module.dependency");
    ChooseElementsDialog dialog = new ChooseElementsDialog(parent, modules, title, description, true) {
      @Override
      protected Icon getItemIcon(final String item) {
        return AllIcons.Nodes.Module;
      }

      @Override
      protected String getItemText(final String item) {
        return item;
      }
    };
    dialog.show();
    for (String module : dialog.getChosenElements()) {
      myModel.addItem(new ModuleDependenciesTableItem(
          new Dependency(Dependency.Scope.COMPILE, Dependency.Type.MODULE, module)));
    }
    myModel.fireTableDataChanged();
  }

  @Override
  public void addNotify() {
    super.addNotify();
    updateButtons();
  }

  private void updateButtons() {
    final int[] selectedRows = myEntryTable.getSelectedRows();
    boolean removeButtonEnabled = true;
    int minRow = myEntryTable.getRowCount() + 1;
    int maxRow = -1;
    for (final int selectedRow : selectedRows) {
      minRow = Math.min(minRow, selectedRow);
      maxRow = Math.max(maxRow, selectedRow);
      final ModuleDependenciesTableItem item = myModel.getItemAt(selectedRow);
      if (!item.isRemovable()) {
        removeButtonEnabled = false;
      }
    }
    if (myRemoveButton != null) {
      myRemoveButton.setEnabled(removeButtonEnabled && selectedRows.length > 0);
    }
  }

  private void removeSelectedItems() {
    if (myEntryTable.isEditing()){
      myEntryTable.getCellEditor().stopCellEditing();
    }
    for (int modelRow = myModel.getRowCount() - 1; modelRow >= 0; modelRow--) {
      if (myEntryTable.isCellSelected(myEntryTable.convertRowIndexToView(modelRow), 0)) {
        myModel.removeDataRow(modelRow);
      }
    }
    myModel.fireTableDataChanged();
    myModel.setModified();
  }

  private void moveSelectedRows(int increment) {
    if (increment == 0) {
      return;
    }
    if (myEntryTable.isEditing()){
      myEntryTable.getCellEditor().stopCellEditing();
    }
    final ListSelectionModel selectionModel = myEntryTable.getSelectionModel();
    for (int modelRow = increment < 0 ? 0 : myModel.getRowCount() - 1;
         increment < 0 ? modelRow < myModel.getRowCount() : modelRow >= 0;
         modelRow += increment < 0 ? +1 : -1) {
      int visibleRow = myEntryTable.convertRowIndexToView(modelRow);
      if (selectionModel.isSelectedIndex(visibleRow)) {
        int newVisibleRow = myEntryTable.convertRowIndexToView(moveRow(modelRow, increment));
        selectionModel.removeSelectionInterval(visibleRow, visibleRow);
        myModel.fireTableDataChanged();
        selectionModel.addSelectionInterval(newVisibleRow, newVisibleRow);
      }
    }
    Rectangle cellRect = myEntryTable.getCellRect(selectionModel.getMinSelectionIndex(), 0, true);
    myEntryTable.scrollRectToVisible(cellRect);
    myEntryTable.repaint();
  }

  private int moveRow(final int row, final int increment) {
    int newIndex = Math.abs(row + increment) % myModel.getRowCount();
    final ModuleDependenciesTableItem item = myModel.removeDataRow(row);
    myModel.addItemAt(item, newIndex);
    return newIndex;
  }

  @NotNull
  private static CellAppearanceEx getCellAppearance(@NotNull final ModuleDependenciesTableItem item) {
    BuildFileStatement entry = item.getEntry();
    String data = "";
    Icon icon = null;
    if (entry instanceof Dependency) {
      Dependency dependency = (Dependency)entry;
      data = dependency.getValueAsString();
      //noinspection EnumSwitchStatementWhichMissesCases
      switch (dependency.type) {
        case EXTERNAL:
          icon = AndroidIcons.MavenLogo;
          break;
        case FILES:
          icon = PlatformIcons.LIBRARY_ICON;
          break;
        case MODULE:
          icon = AllIcons.Nodes.Module;
          break;
      }
    } else if (entry != null) {
      data = entry.toString();
    }
    return SimpleTextCellAppearance.regular(data, icon);
  }

  @Override
  public void apply() {
    List items = myModel.getItems();
    final List dependencies = Lists.newArrayListWithExpectedSize(items.size());
    for (ModuleDependenciesTableItem item : items) {
      dependencies.add(item.getEntry());
    }
    try {
      ActionRunner.runInsideWriteAction(new ActionRunner.InterruptibleRunnable() {
        @Override
        public void run() throws Exception {
          myGradleBuildFile.setValue(BuildFileKey.DEPENDENCIES, dependencies);
        }
      });
    }
    catch (Exception e) {
      LOG.error("Unable to commit dependency changes", e);
    }
    myModel.resetModified();
  }

  @Override
  public boolean isModified() {
    return myModel.isModified();
  }

  public void select(@NotNull GradleCoordinate dependency) {
    int row = myModel.getRow(dependency);
    if (row >= 0) {
      myEntryTable.getSelectionModel().setSelectionInterval(row, row);
    }
  }

  private static class TableItemRenderer extends ColoredTableCellRenderer {
    private final Border NO_FOCUS_BORDER = BorderFactory.createEmptyBorder(1, 1, 1, 1);

    @Override
    protected void customizeCellRenderer(JTable table, @Nullable Object value, boolean selected, boolean hasFocus,
                                         int row, int column) {
      setPaintFocusBorder(false);
      setFocusBorderAroundIcon(true);
      setBorder(NO_FOCUS_BORDER);
      if (value != null && value instanceof ModuleDependenciesTableItem) {
        final ModuleDependenciesTableItem tableItem = (ModuleDependenciesTableItem)value;
        getCellAppearance(tableItem).customize(this);
        setToolTipText(tableItem.getTooltipText());
      }
    }
  }

  private abstract static class PopupAction implements Runnable {
    private Icon myIcon;
    private Object myIndex;
    private Object myTitle;

    protected PopupAction(Icon icon, Object index, Object title) {
      myIcon = icon;
      myIndex = index;
      myTitle = title;
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy