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

com.android.tools.idea.welcome.SdkComponentsStep Maven / Gradle / Ivy

/*
 * 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.welcome;

import com.android.tools.idea.sdk.DefaultSdks;
import com.android.tools.idea.templates.TemplateUtils;
import com.android.tools.idea.wizard.ScopedStateStore;
import com.android.tools.idea.wizard.WizardConstants;
import com.android.tools.idea.wizard.WizardUtils;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
import com.intellij.openapi.ui.Splitter;
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.ui.JBColor;
import com.intellij.ui.ScrollPaneFactory;
import com.intellij.ui.table.JBTable;
import com.intellij.uiDesigner.core.GridConstraints;
import com.intellij.uiDesigner.core.GridLayoutManager;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.android.sdk.AndroidSdkData;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.util.Arrays;
import java.util.Set;

/**
 * Wizard page for selecting SDK components to download.
 */
public class SdkComponentsStep extends FirstRunWizardStep {
  public static final String FIELD_SDK_LOCATION = "SDK location";

  private final InstallableComponent[] myInstallableComponents;
  @NotNull private final FirstRunWizardMode myMode;
  @NotNull private final ScopedStateStore.Key myKeyCustomInstall;
  private JPanel myContents;
  private JBTable myComponentsTable;
  private JTextPane myComponentDescription;
  private JLabel myNeededSpace;
  private JLabel myAvailableSpace;
  private JLabel myErrorMessage;
  private ScopedStateStore.Key mySdkDownloadPathKey;
  private TextFieldWithBrowseButton myPath;
  private JPanel myBody;
  private boolean myUserEditedPath = false;

  public SdkComponentsStep(@NotNull InstallableComponent[] components,
                           @NotNull ScopedStateStore.Key keyCustomInstall,
                           @NotNull ScopedStateStore.Key sdkDownloadPathKey,
                           @NotNull FirstRunWizardMode mode) {
    super("SDK Components Setup");
    myMode = mode;
    myKeyCustomInstall = keyCustomInstall;

    myPath.addBrowseFolderListener("Android SDK", "Select Android SDK install directory", null,
                                   FileChooserDescriptorFactory.createSingleFolderDescriptor());

    mySdkDownloadPathKey = sdkDownloadPathKey;
    Font labelFont = UIUtil.getLabelFont();
    Font smallLabelFont = labelFont.deriveFont(labelFont.getSize() - 1.0f);
    myNeededSpace.setFont(smallLabelFont);
    myAvailableSpace.setFont(smallLabelFont);
    myErrorMessage.setText(null);
    myErrorMessage.setForeground(JBColor.red);

    myInstallableComponents = components;
    DefaultTableModel model = new DefaultTableModel(0, 1) {
      @Override
      public void setValueAt(Object aValue, int row, int column) {
        boolean isSelected = ((Boolean)aValue);
        InstallableComponent installableComponent = myInstallableComponents[row];
        if (isSelected) {
          select(installableComponent);
        }
        else {
          deselect(installableComponent);
        }
        fireTableRowsUpdated(row, row);
      }
    };
    for (InstallableComponent installableComponent : myInstallableComponents) {
      model.addRow(new Object[]{installableComponent});
    }
    myComponentsTable.setModel(model);
    myComponentsTable.setTableHeader(null);
    myComponentsTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
      @Override
      public void valueChanged(ListSelectionEvent e) {
        int selected = myComponentsTable.getSelectedRow();
        String description = selected >= 0 ? myInstallableComponents[selected].getDescription() : null;
        myComponentDescription.setText(description);
      }
    });
    TableColumn column = myComponentsTable.getColumnModel().getColumn(0);
    column.setCellRenderer(new SdkComponentRenderer());
    column.setCellEditor(new SdkComponentRenderer());
    setComponent(myContents);
  }

  private static boolean isChild(@Nullable InstallableComponent child, @NotNull InstallableComponent installableComponent) {
    return child != null && (child == installableComponent || isChild(child.getParent(), installableComponent));
  }

  @Nullable
  private static File getExistingParentFile(@Nullable String path) {
    if (StringUtil.isEmpty(path)) {
      return null;
    }
    File file = new File(path).getAbsoluteFile();
    while (file != null && !file.exists()) {
      file = file.getParentFile();
    }
    return file;
  }

  private static String getDiskSpace(@Nullable String path) {
    File file = getTargetFilesystem(path);
    if (file == null) {
      return "";
    }
    String available = WelcomeUIUtils.getSizeLabel(file.getFreeSpace());
    if (SystemInfo.isWindows) {
      while (file.getParentFile() != null) {
        file = file.getParentFile();
      }
      return String.format("Disk space available on drive %s: %s", file.getName(), available);
    }
    else {
      return String.format("Available disk space: %s", available);
    }
  }

  @Nullable
  private static File getTargetFilesystem(@Nullable String path) {
    File file = getExistingParentFile(path);
    if (file == null) {
      File[] files = File.listRoots();
      if (files.length != 0) {
        file = files[0];
      }
    }
    return file;
  }

  @Override
  public boolean validate() {
    String path = myState.get(mySdkDownloadPathKey);
    if (!StringUtil.isEmpty(path)) {
      myUserEditedPath = true;
    }
    WizardUtils.ValidationResult error = WizardUtils.validateLocation(path, FIELD_SDK_LOCATION, false);
    String message = error.isOk() ? null : error.getFormattedMessage();
    boolean isOk = !error.isError();
    if (isOk) {
      File filesystem = getTargetFilesystem(path);
      if (!(filesystem == null || filesystem.getFreeSpace() > getComponentsSize())) {
        isOk = false;
        message = "Target drive does not have enough free space";
      }
      else if (isNonEmptyNonSdk(path)) {
        isOk = true;
        message = "Target folder is neither empty nor does it point to an existing SDK installation.";
      }
      else if (!StringUtil.isEmptyOrSpaces(path)) {
        File file = new File(path);
        if (file.isDirectory() && DefaultSdks.isValidAndroidSdkPath(file)) {
          isOk = true;
          message = "An existing Android SDK was detected. The setup wizard will only download missing or outdated SDK components.";
        }
      }
    }
    setErrorHtml(myUserEditedPath ? message : null);
    return isOk;
  }

  private static boolean isNonEmptyNonSdk(@Nullable String path) {
    if (path == null) {
      return false;
    }
    File file = new File(path);
    if (file.exists() && TemplateUtils.listFiles(file).length > 0) {
      return AndroidSdkData.getSdkData(file) == null;
    }
    return false;
  }

  @Override
  public void deriveValues(Set modified) {
    for (int i = 0; i < myInstallableComponents.length; i++) {
      ScopedStateStore.Key key = myInstallableComponents[i].getKey();
      if (modified.contains(key)) {
        myComponentsTable.getModel().setValueAt(myState.getNotNull(key, true), i, 0);
      }
    }
    myAvailableSpace.setText(getDiskSpace(myState.get(mySdkDownloadPathKey)));
    long selected = getComponentsSize();
    myNeededSpace.setText(String.format("Total disk space required: %s", WelcomeUIUtils.getSizeLabel(selected)));
    super.deriveValues(modified);
  }

  private long getComponentsSize() {
    long selected = 0;
    for (InstallableComponent installableComponent : myInstallableComponents) {
      if (isSelected(installableComponent)) {
        selected += installableComponent.getSize();
      }
    }
    return selected;
  }

  private void deselect(InstallableComponent installableComponent) {
    for (InstallableComponent child : myInstallableComponents) {
      if (child.getSize() > 0 && isChild(child, installableComponent)) {
        myState.put(child.getKey(), false);
      }
    }
  }

  private Iterable getChildren(final InstallableComponent installableComponent) {
    return Iterables.filter(Arrays.asList(myInstallableComponents), new Predicate() {
      @Override
      public boolean apply(@Nullable InstallableComponent input) {
        assert input != null;
        InstallableComponent n = input;
        do {
          if (n == installableComponent) {
            return true;
          }
          n = n.getParent();
        }
        while (n != null);
        return false;
      }
    });
  }

  private void select(InstallableComponent installableComponent) {
    for (InstallableComponent child : getChildren(installableComponent)) {
      myState.put(child.getKey(), true);
    }
  }

  @Override
  public void init() {
    register(mySdkDownloadPathKey, myPath);
    if (myInstallableComponents.length > 0) {
      myComponentsTable.getSelectionModel().setSelectionInterval(0, 0);
    }
  }

  @NotNull
  @Override
  public JLabel getMessageLabel() {
    return myErrorMessage;
  }

  @Override
  public JComponent getPreferredFocusedComponent() {
    return myComponentsTable;
  }

  private boolean isSelected(InstallableComponent installableComponent) {
    for (InstallableComponent child : getChildren(installableComponent)) {
      if (!myState.getNotNull(child.getKey(), true)) {
        return false;
      }
    }
    return true;
  }

  @Override
  public boolean isStepVisible() {
    return !myMode.hasValidSdkLocation() && myState.getNotNull(myKeyCustomInstall, true);
  }

  private void createUIComponents() {
    Splitter splitter = new Splitter(false, 0.5f, 0.2f, 0.8f);
    myBody = splitter;
    myComponentsTable = new JBTable();
    myComponentDescription = new JTextPane();
    splitter.setShowDividerIcon(false);
    splitter.setShowDividerControls(false);
    splitter.setFirstComponent(ScrollPaneFactory.createScrollPane(myComponentsTable, false));
    splitter.setSecondComponent(ScrollPaneFactory.createScrollPane(myComponentDescription, false));

    myComponentDescription.setFont(UIUtil.getLabelFont());
    myComponentDescription.setEditable(false);
    myComponentDescription.setBorder(BorderFactory.createEmptyBorder(WizardConstants.STUDIO_WIZARD_INSET_SIZE,
                                                                     WizardConstants.STUDIO_WIZARD_INSET_SIZE,
                                                                     WizardConstants.STUDIO_WIZARD_INSET_SIZE,
                                                                     WizardConstants.STUDIO_WIZARD_INSET_SIZE));
  }

  private final class SdkComponentRenderer extends AbstractCellEditor implements TableCellRenderer, TableCellEditor {
    private final JPanel myPanel;
    private final JCheckBox myCheckBox;
    private Border myEmptyBorder;

    public SdkComponentRenderer() {
      myPanel = new JPanel(new GridLayoutManager(1, 1));
      myCheckBox = new JCheckBox();
      myCheckBox.setOpaque(false);
      myCheckBox.addActionListener(new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
          stopCellEditing();
        }
      });
    }

    @Override
    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
      setupControl(table, value, isSelected, hasFocus);
      return myPanel;
    }

    private void setupControl(JTable table, Object value, boolean isSelected, boolean hasFocus) {
      myPanel.setBorder(getCellBorder(table, isSelected && hasFocus));
      Color foreground;
      Color background;
      if (isSelected) {
        background = table.getSelectionBackground();
        foreground = table.getSelectionForeground();
      }
      else {
        background = table.getBackground();
        foreground = table.getForeground();
      }
      myPanel.setBackground(background);
      myCheckBox.setForeground(foreground);
      myPanel.remove(myCheckBox);
      InstallableComponent installableComponent = (InstallableComponent)value;
      int indent = 0;
      if (installableComponent != null) {
        myCheckBox.setEnabled(installableComponent.isOptional());
        myCheckBox.setText(installableComponent.getLabel());
        myCheckBox.setSelected(isSelected((InstallableComponent)value));
        //noinspection ConstantConditions
        while (installableComponent.getParent() != null) {
          indent++;
          installableComponent = installableComponent.getParent();
        }
      }
      myPanel.add(myCheckBox,
                  new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED,
                                      GridConstraints.SIZEPOLICY_FIXED, null, null, null, indent * 2));
    }

    private Border getCellBorder(JTable table, boolean isSelectedFocus) {
      Border focusedBorder = UIUtil.getTableFocusCellHighlightBorder();
      Border border;
      if (isSelectedFocus) {
        border = focusedBorder;
      }
      else {
        if (myEmptyBorder == null) {
          myEmptyBorder = new EmptyBorder(focusedBorder.getBorderInsets(table));
        }
        border = myEmptyBorder;
      }
      return border;
    }

    @Override
    public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
      setupControl(table, value, true, true);
      return myPanel;
    }

    @Override
    public Object getCellEditorValue() {
      return myCheckBox.isSelected();
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy