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

com.android.tools.idea.wizard.ConfigureAndroidProjectStep Maven / Gradle / Ivy

Go to download

A packaging of the IntelliJ Community Edition android library. This is release number 1 of trunk branch 142.

The newest version!
/*
 * 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.wizard;

import com.android.annotations.VisibleForTesting;
import com.android.tools.idea.templates.TemplateUtils;
import com.google.common.base.CharMatcher;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.intellij.ide.util.PropertiesComponent;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.fileChooser.FileChooserFactory;
import com.intellij.openapi.fileChooser.FileSaverDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileWrapper;
import com.intellij.ui.JBColor;
import org.jetbrains.android.util.AndroidUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import javax.swing.text.Document;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.util.ArrayList;
import java.util.Set;

import static com.android.tools.idea.wizard.ScopedStateStore.Key;

/**
 * ConfigureAndroidModuleStep is the first page in the New Project wizard that sets project/module name, location, and other project-global
 * parameters.
 */
public class ConfigureAndroidProjectStep extends DynamicWizardStepWithHeaderAndDescription {


  private static final String EXAMPLE_DOMAIN = "example.com";
  public static final String SAVED_COMPANY_DOMAIN = "SAVED_COMPANY_DOMAIN";
  private static final CharMatcher ILLEGAL_CHARACTER_MATCHER = CharMatcher.anyOf(WizardConstants.INVALID_FILENAME_CHARS);

  @VisibleForTesting
  static final Set INVALID_MSFT_FILENAMES = ImmutableSet
    .of("con", "prn", "aux", "clock$", "nul", "com1", "com2", "com3", "com4", "com5", "com6", "com7", "com8", "com9", "lpt1", "lpt2",
        "lpt3", "lpt4", "lpt5", "lpt6", "lpt7", "lpt8", "lpt9", "$mft", "$mftmirr", "$logfile", "$volume", "$attrdef", "$bitmap", "$boot",
        "$badclus", "$secure", "$upcase", "$extend", "$quota", "$objid", "$reparse");


  private TextFieldWithBrowseButton myProjectLocation;
  private JTextField myAppName;
  private JPanel myPanel;
  private JTextField myCompanyDomain;
  private LabelWithEditLink myPackageName;

  public ConfigureAndroidProjectStep(@NotNull Disposable disposable) {
    this("Configure your new project", disposable);
  }

  public ConfigureAndroidProjectStep(String title, Disposable parentDisposable) {
    super(title, null, parentDisposable);
    setBodyComponent(myPanel);
  }

  @Override
  public void init() {
    register(WizardConstants.APPLICATION_NAME_KEY, myAppName);
    register(WizardConstants.COMPANY_DOMAIN_KEY, myCompanyDomain);
    register(WizardConstants.PACKAGE_NAME_KEY, myPackageName, new ComponentBinding() {
      @Override
      public void setValue(@Nullable String newValue, @NotNull LabelWithEditLink component) {
        newValue = newValue == null ? "" : newValue;
        component.setText(newValue);
      }

      @Nullable
      @Override
      public String getValue(@NotNull LabelWithEditLink component) {
        return component.getText();
      }

      @Nullable
      @Override
      public Document getDocument(@NotNull LabelWithEditLink component) {
        return component.getDocument();
      }
    });
    registerValueDeriver(WizardConstants.PACKAGE_NAME_KEY, PACKAGE_NAME_DERIVER);
    register(WizardConstants.PROJECT_LOCATION_KEY, myProjectLocation);
    registerValueDeriver(WizardConstants.PROJECT_LOCATION_KEY, myProjectLocationDeriver);

    myProjectLocation.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent e) {
        browseForFile();
      }
    });

    myState.put(WizardConstants.APPLICATION_NAME_KEY, "My Application");
    String savedCompanyDomain = PropertiesComponent.getInstance().getValue(SAVED_COMPANY_DOMAIN);
    if (savedCompanyDomain == null) {
      savedCompanyDomain = nameToPackage(System.getProperty("user.name"));
      if (savedCompanyDomain != null) {
        savedCompanyDomain = savedCompanyDomain + '.' + EXAMPLE_DOMAIN;
      }
    }
    if (savedCompanyDomain == null) {
      savedCompanyDomain = EXAMPLE_DOMAIN;
    }
    myState.put(WizardConstants.COMPANY_DOMAIN_KEY, savedCompanyDomain);
    super.init();
  }

  private void browseForFile() {
    FileSaverDescriptor fileSaverDescriptor = new FileSaverDescriptor("Project location", "Please choose a location for your project");
    File currentPath = new File(myProjectLocation.getText());
    File parentPath = currentPath.getParentFile();
    if (parentPath == null) {
      String homePath = System.getProperty("user.home");
      parentPath = homePath == null ? new File("/") : new File(homePath);
    }
    VirtualFile parent = LocalFileSystem.getInstance().findFileByIoFile(parentPath);
    String filename = currentPath.getName();
    VirtualFileWrapper fileWrapper =
      FileChooserFactory.getInstance().createSaveFileDialog(fileSaverDescriptor, (Project)null).save(parent, filename);
    if (fileWrapper != null) {
      myProjectLocation.setText(fileWrapper.getFile().getAbsolutePath());
    }
  }

  @Override
  public void deriveValues(Set modified) {
    super.deriveValues(modified);
    // Save the user edited value of the company domain
    if (modified.contains(WizardConstants.COMPANY_DOMAIN_KEY)) {
      String domain = myState.get(WizardConstants.COMPANY_DOMAIN_KEY);
      if (domain != null && !domain.isEmpty() && myState.containsKey(WizardConstants.PACKAGE_NAME_KEY)) {
        @SuppressWarnings("ConstantConditions")
        String message = AndroidUtils.validateAndroidPackageName(myState.get(WizardConstants.PACKAGE_NAME_KEY));
        if (message == null) {
          PropertiesComponent.getInstance().setValue(SAVED_COMPANY_DOMAIN, domain);
        }
      }
    }
  }

  @Override
  public boolean validate() {
    WizardUtils.ValidationResult locationValidationResult = WizardUtils.validateLocation(myState.get(WizardConstants.PROJECT_LOCATION_KEY));
    setErrorHtml(locationValidationResult.isOk() ? "" : locationValidationResult.getFormattedMessage());
    return validateAppName() && validatePackageName() && !locationValidationResult.isError();
  }

  protected boolean validateAppName() {
    String appName = myState.get(WizardConstants.APPLICATION_NAME_KEY);
    if (appName == null || appName.isEmpty()) {
      setErrorHtml("Please enter an application name (shown in launcher)");
      return false;
    } else if (Character.isLowerCase(appName.charAt(0))) {
      setErrorHtml("The application name for most apps begins with an uppercase letter");
    }
    return true;
  }

  protected boolean validatePackageName() {
    String packageName = myState.get(WizardConstants.PACKAGE_NAME_KEY);
    if (packageName == null) {
      setErrorHtml("Please enter a package name (This package uniquely identifies your application)");
      return false;
    } else {
      String message = AndroidUtils.validateAndroidPackageName(packageName);
      if (message != null) {
        setErrorHtml("Invalid package name: " + message);
        return false;
      }
    }
    return true;
  }

  @NotNull
  @Override
  public String getStepName() {
    return "Create Android Project";
  }

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

  @Nullable
  public String getHelpText(@NotNull Key key) {
    if (key.equals(WizardConstants.APPLICATION_NAME_KEY)) {
      return "The application name is shown in the Play store, as well as in the Manage Applications list in Settings.";
    } else if (key.equals(WizardConstants.PACKAGE_NAME_KEY)) {
      return "The package name must be a unique identifier for your application.\n It is typically not shown to users, " +
             "but it must stay the same for the lifetime of your application; it is how multiple versions of the same application " +
             "are considered the \"same app\".\nThis is typically the reverse domain name of your organization plus one or more " +
             "application identifiers, and it must be a valid Java package name.";
    } else {
      return null;
    }
  }

  @VisibleForTesting
  static String nameToPackage(String name) {
    name = name.replace('-', '_');
    name = name.replaceAll("[^a-zA-Z0-9_]", "");
    name = name.toLowerCase();
    return name;
  }

  public static final ValueDeriver PACKAGE_NAME_DERIVER = new ValueDeriver() {
    @Nullable
    @Override
    public Set> getTriggerKeys() {
      return makeSetOf(WizardConstants.APPLICATION_NAME_KEY, WizardConstants.COMPANY_DOMAIN_KEY);
    }

    @Nullable
    @Override
    public String deriveValue(@NotNull ScopedStateStore state, Key changedKey, @Nullable String currentValue) {
      String projectName = state.get(WizardConstants.APPLICATION_NAME_KEY);
      if (projectName == null) {
        projectName = "app";
      }
      projectName = nameToPackage(projectName);
      String companyDomain = state.get(WizardConstants.COMPANY_DOMAIN_KEY);
      if (companyDomain == null) {
        companyDomain = EXAMPLE_DOMAIN;
      }
      ArrayList domainParts = Lists.newArrayList(companyDomain.split("\\."));
      String reversedDomain = Joiner.on('.').skipNulls().join(Lists.reverse(Lists.transform(domainParts, new Function() {
        @Override
        public String apply(String input) {
          String name = nameToPackage(input);
          return name.isEmpty() ? null : name;
        }
      })));
      return reversedDomain + '.' + projectName;
    }
  };

  private static final ValueDeriver myProjectLocationDeriver = new ValueDeriver() {
    @Nullable
    @Override
    public Set> getTriggerKeys() {
      return makeSetOf(WizardConstants.APPLICATION_NAME_KEY);
    }

    @Nullable
    @Override
    public String deriveValue(ScopedStateStore state, Key changedKey, @Nullable String currentValue) {
      String name = state.get(WizardConstants.APPLICATION_NAME_KEY);
      name = name == null ? "" : name;
      name = name.replaceAll(WizardConstants.INVALID_FILENAME_CHARS, "");
      name = name.replaceAll("\\s", "");
      File baseDirectory = new File(NewProjectWizardState.getProjectFileDirectory());
      File projectDir = new File(baseDirectory, name);
      int i = 2;
      while (projectDir.exists()) {
        projectDir = new File(baseDirectory, name + i);
        i++;
      }
      return projectDir.getPath();
    }
  };

  @NotNull
  @Override
  protected WizardStepHeaderSettings getStepHeader() {
    return ConfigureAndroidProjectPath.buildConfigurationHeader();
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy