blob: a963fd6a612e370fc8d9ccdf13db1fbf272a60a1 [file] [log] [blame]
/*
* Copyright (C) 2015 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.updater.configure;
import com.android.sdklib.AndroidVersion;
import com.android.sdklib.repository.descriptors.IPkgDesc;
import com.android.sdklib.repository.descriptors.PkgType;
import com.android.tools.idea.sdk.*;
import com.android.tools.idea.sdk.remote.RemoteSdk;
import com.android.tools.idea.sdk.remote.UpdatablePkgInfo;
import com.android.tools.idea.sdk.remote.internal.sources.SdkSources;
import com.android.tools.idea.stats.UsageTracker;
import com.android.tools.idea.welcome.config.FirstRunWizardMode;
import com.android.tools.idea.welcome.install.FirstRunWizardDefaults;
import com.android.tools.idea.welcome.wizard.InstallComponentsPath;
import com.android.tools.idea.welcome.wizard.ConsolidatedProgressStep;
import com.android.tools.idea.wizard.dynamic.DialogWrapperHost;
import com.android.tools.idea.wizard.dynamic.DynamicWizard;
import com.android.tools.idea.wizard.dynamic.DynamicWizardHost;
import com.android.tools.idea.wizard.dynamic.SingleStepPath;
import com.android.utils.ILogger;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.google.common.collect.TreeMultimap;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.options.ShowSettingsUtil;
import com.intellij.openapi.updateSettings.impl.UpdateSettingsConfigurable;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.ui.HyperlinkAdapter;
import com.intellij.ui.HyperlinkLabel;
import com.intellij.ui.JBColor;
import com.intellij.ui.components.JBLabel;
import com.intellij.ui.components.JBTabbedPane;
import com.intellij.ui.dualView.TreeTableView;
import com.intellij.ui.table.SelectionProvider;
import com.intellij.util.ui.tree.TreeUtil;
import org.jetbrains.android.actions.RunAndroidSdkManagerAction;
import org.jetbrains.android.sdk.AndroidSdkData;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import sun.awt.CausedFocusEvent;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.HyperlinkEvent;
import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.util.Collection;
import java.util.List;
import java.util.Set;
/**
* Main panel for {@link SdkUpdaterConfigurable}
*/
public class SdkUpdaterConfigPanel {
private JPanel myRootPane;
private JTextField mySdkLocation;
private PlatformComponentsPanel myPlatformComponentsPanel;
private ToolComponentsPanel myToolComponentsPanel;
private UpdateSitesPanel myUpdateSitesPanel;
private HyperlinkLabel myLaunchStandaloneLink;
private HyperlinkLabel myChannelLink;
private HyperlinkLabel myEditSdkLink;
private JBTabbedPane myTabPane;
private JPanel mySdkLocationPanel;
private JBLabel mySdkLocationLabel;
private JBLabel mySdkErrorLabel;
private SdkSources mySdkSources;
private Runnable mySourcesChangeListener = new DispatchRunnable() {
@Override
public void doRun() {
refresh();
}
};
private SdkState mySdkState;
private boolean myHasPreview;
private boolean myIncludePreview;
SdkLoadedCallback myUpdater = new SdkLoadedCallback(true) {
@Override
public void doRun(@NotNull SdkPackages packages) {
updateItems(packages);
}
};
public interface MultiStateRow {
void cycleState();
}
public SdkUpdaterConfigPanel(SdkState sdkState, final Runnable channelChangedCallback) {
UsageTracker.getInstance().trackEvent(UsageTracker.CATEGORY_SDK_MANAGER, UsageTracker.ACTION_SDK_MANAGER_LOADED, null, null);
mySdkState = sdkState;
ILogger logger = new LogWrapper(Logger.getInstance(getClass()));
mySdkSources = mySdkState.getRemoteSdk().fetchSources(RemoteSdk.DEFAULT_EXPIRATION_PERIOD_MS, logger);
mySdkSources.addChangeListener(mySourcesChangeListener);
myUpdateSitesPanel.setSdkState(sdkState);
myLaunchStandaloneLink.setHyperlinkText("Launch Standalone SDK Manager");
myLaunchStandaloneLink.addHyperlinkListener(new HyperlinkAdapter() {
@Override
protected void hyperlinkActivated(HyperlinkEvent e) {
RunAndroidSdkManagerAction.runSpecificSdkManager(null, IdeSdks.getAndroidSdkPath());
}
});
myChannelLink.setHyperlinkText("Preview packages available! ", "Switch", " to Preview Channel to see them");
myChannelLink.addHyperlinkListener(new HyperlinkAdapter() {
@Override
protected void hyperlinkActivated(HyperlinkEvent e) {
UpdateSettingsConfigurable settings = new UpdateSettingsConfigurable();
settings.setCheckNowEnabled(false);
ShowSettingsUtil.getInstance().editConfigurable(getComponent(), settings);
channelChangedCallback.run();
}
});
myEditSdkLink.setHyperlinkText("Edit");
myEditSdkLink.addHyperlinkListener(new HyperlinkAdapter() {
@Override
protected void hyperlinkActivated(HyperlinkEvent e) {
final DynamicWizardHost host = new DialogWrapperHost(null);
DynamicWizard wizard = new DynamicWizard(null, null, "SDK Setup", host) {
@Override
public void init() {
ConsolidatedProgressStep progressStep = new ConsolidatedProgressStep(myHost.getDisposable(), host);
String sdkPath = mySdkLocation.getText();
File location;
if (StringUtil.isEmpty(sdkPath)) {
location = FirstRunWizardDefaults.getInitialSdkLocation(FirstRunWizardMode.MISSING_SDK);
}
else {
location = new File(sdkPath);
}
InstallComponentsPath path =
new InstallComponentsPath(progressStep, FirstRunWizardMode.MISSING_SDK, location,
mySdkState.getPackages().getRemotePkgInfos());
progressStep.setPaths(Lists.newArrayList(path));
addPath(path);
addPath(new SingleStepPath(progressStep));
super.init();
}
@Override
public void performFinishingActions() {
final File newPath = IdeSdks.getAndroidSdkPath();
if (newPath != null) {
mySdkState = SdkState.getInstance(AndroidSdkData.getSdkData(newPath));
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
mySdkLocation.setText(newPath.getAbsolutePath());
refresh();
}
});
}
}
@NotNull
@Override
protected String getProgressTitle() {
return "Setting up SDK...";
}
@Override
protected String getWizardActionDescription() {
return "Setting up SDK...";
}
};
wizard.init();
wizard.show();
}
});
mySdkLocation.setEditable(false);
mySdkErrorLabel.setIcon(AllIcons.General.BalloonError);
mySdkErrorLabel.setForeground(JBColor.RED);
}
public void setIncludePreview(boolean includePreview) {
myIncludePreview = includePreview;
myChannelLink.setVisible(myHasPreview && !myIncludePreview);
myPlatformComponentsPanel.setIncludePreview(includePreview);
myToolComponentsPanel.setIncludePreview(includePreview);
loadPackages(mySdkState.getPackages());
}
public JComponent getComponent() {
return myRootPane;
}
public boolean isModified() {
return myPlatformComponentsPanel.isModified() || myToolComponentsPanel.isModified() || myUpdateSitesPanel.isModified();
}
static void setTreeTableProperties(final TreeTableView tt, UpdaterTreeNode.Renderer renderer, final ChangeListener listener) {
tt.setTreeCellRenderer(renderer);
new CheckboxClickListener(tt, renderer).installOn(tt);
TreeUtil.installActions(tt.getTree());
tt.getTree().setToggleClickCount(0);
tt.getTree().setShowsRootHandles(true);
setTableProperties(tt, listener);
}
static void setTableProperties(@NotNull final JTable table, @Nullable final ChangeListener listener) {
assert table instanceof SelectionProvider;
ActionMap am = table.getActionMap();
final CycleAction forwardAction = new CycleAction(false);
final CycleAction backwardAction = new CycleAction(true);
am.put("selectPreviousColumnCell", backwardAction);
am.put("selectNextColumnCell", forwardAction);
table.addKeyListener(new KeyAdapter() {
@Override
public void keyTyped(KeyEvent e) {
if (e.getKeyChar() == KeyEvent.VK_ENTER || e.getKeyChar() == KeyEvent.VK_SPACE) {
List<MultiStateRow> selection = (List<MultiStateRow>)((SelectionProvider)table).getSelection();
for (MultiStateRow node : selection) {
node.cycleState();
table.repaint();
if (listener != null) {
listener.stateChanged(new ChangeEvent(node));
}
}
}
}
});
table.addFocusListener(new FocusListener() {
@Override
public void focusLost(FocusEvent e) {
if (e.getOppositeComponent() != null) {
table.getSelectionModel().clearSelection();
}
}
@Override
public void focusGained(FocusEvent e) {
JTable table = (JTable)e.getSource();
if (table.getSelectionModel().getMinSelectionIndex() != -1) {
return;
}
if (e instanceof CausedFocusEvent && ((CausedFocusEvent)e).getCause() == CausedFocusEvent.Cause.TRAVERSAL_BACKWARD) {
backwardAction.doAction(table);
}
else {
forwardAction.doAction(table);
}
}
});
}
public void refresh() {
validate();
myPlatformComponentsPanel.startLoading();
myToolComponentsPanel.startLoading();
myUpdateSitesPanel.startLoading();
SdkLoadedCallback remoteComplete = new SdkLoadedCallback(true) {
@Override
public void doRun(@NotNull SdkPackages packages) {
updateItems(packages);
myPlatformComponentsPanel.finishLoading();
myToolComponentsPanel.finishLoading();
myUpdateSitesPanel.finishLoading();
}
};
mySdkState.loadAsync(SdkState.DEFAULT_EXPIRATION_PERIOD_MS, false, myUpdater, remoteComplete, null, true);
}
private void validate() {
AndroidSdkData data = mySdkState.getSdkData();
File sdkLocation = null;
if (data != null) {
sdkLocation = data.getLocation();
}
boolean valid = sdkLocation != null;
myTabPane.setEnabled(valid);
myPlatformComponentsPanel.setEnabled(valid);
mySdkLocationLabel.setForeground(valid ? JBColor.foreground() : JBColor.RED);
mySdkErrorLabel.setVisible(!valid);
}
private void loadPackages(SdkPackages packages) {
Multimap<AndroidVersion, UpdatablePkgInfo> platformPackages = TreeMultimap.create();
Set<UpdatablePkgInfo> buildToolsPackages = Sets.newTreeSet();
Set<UpdatablePkgInfo> toolsPackages = Sets.newTreeSet();
for (UpdatablePkgInfo info : packages.getConsolidatedPkgs().values()) {
IPkgDesc desc = info.getPkgDesc(myIncludePreview);
if (desc == null) {
// We're not looking for previews, and this only has a preview available.
continue;
}
AndroidVersion version = desc.getAndroidVersion();
PkgType type = info.getPkgDesc(myIncludePreview).getType();
if (type == PkgType.PKG_SAMPLE) {
continue;
}
if (version != null && type != PkgType.PKG_DOC) {
platformPackages.put(version, info);
}
else if (type == PkgType.PKG_BUILD_TOOLS) {
buildToolsPackages.add(info);
}
else {
toolsPackages.add(info);
}
if (info.hasPreview()) {
myHasPreview = true;
}
}
myChannelLink.setVisible(myHasPreview && !myIncludePreview);
myPlatformComponentsPanel.setPackages(platformPackages);
myToolComponentsPanel.setPackages(toolsPackages, buildToolsPackages);
}
private void updateItems(SdkPackages packages) {
myPlatformComponentsPanel.clearState();
myToolComponentsPanel.clearState();
loadPackages(packages);
}
public Collection<NodeStateHolder> getStates() {
List<NodeStateHolder> result = Lists.newArrayList();
result.addAll(myPlatformComponentsPanel.myStates);
result.addAll(myToolComponentsPanel.myStates);
return result;
}
public void reset() {
refresh();
File path = IdeSdks.getAndroidSdkPath();
if (path != null) {
mySdkLocation.setText(path.getPath());
}
myPlatformComponentsPanel.reset();
myToolComponentsPanel.reset();
myUpdateSitesPanel.reset();
}
public void disposeUIResources() {
mySdkSources.removeChangeListener(mySourcesChangeListener);
}
public void saveSources() {
myUpdateSitesPanel.save();
}
private static class CycleAction extends AbstractAction {
boolean myBackward;
CycleAction(boolean backward) {
myBackward = backward;
}
@Override
public void actionPerformed(ActionEvent evt) {
doAction((JTable)evt.getSource());
}
public void doAction(JTable table) {
KeyboardFocusManager manager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
ListSelectionModel selectionModel = table.getSelectionModel();
int row = myBackward ? selectionModel.getMinSelectionIndex() : selectionModel.getMaxSelectionIndex();
if (row == -1) {
if (myBackward) {
row = table.getRowCount();
}
}
row += myBackward ? -1 : 1;
if (row < 0) {
manager.focusPreviousComponent(table);
}
else if (row >= table.getRowCount()) {
manager.focusNextComponent(table);
}
else {
selectionModel.setSelectionInterval(row, row);
table.setColumnSelectionInterval(1, 1);
table.scrollRectToVisible(table.getCellRect(row, 1, true));
}
table.repaint();
}
}
}