blob: 90eb11e49c9b98f71d6c629df0de167b9205d81c [file] [log] [blame]
/*
* 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.wizard;
import com.android.sdklib.devices.Storage;
import com.android.tools.idea.welcome.install.FirstRunWizardDefaults;
import com.android.tools.idea.welcome.install.Haxm;
import com.android.tools.idea.wizard.dynamic.ScopedStateStore;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.event.ChangeListener;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Hashtable;
import static com.android.tools.idea.welcome.install.Haxm.UI_UNITS;
/**
* Wizard page for setting up IntelĀ® HAXM settings for Mac OS X platform
*/
public final class HaxmInstallSettingsStep extends FirstRunWizardStep {
private static final int MAJOR_TICKS = 4;
private static final int MINOR_TICKS = 512;
// Smallest adjustment user will be able to make with a slider (if the RAM size is small enough)
private static final int MAX_TICK_RESOLUTION = 32; //Mb
private static final int MIN_EMULATOR_MEMORY = 512; //Mb
private final ScopedStateStore.Key<Integer> myKeyEmulatorMemory;
private final int myRecommendedMemorySize;
private final ScopedStateStore.Key<Boolean> myKeyCustomInstall;
private final ScopedStateStore.Key<Boolean> myKeyInstallHaxm;
private JPanel myRoot;
private com.intellij.ui.HyperlinkLabel myIntelHAXMDocumentationButton;
private JSlider myMemorySlider;
private JSpinner myMemorySize;
private JLabel myUnitLabel;
private JButton myRecommended;
public HaxmInstallSettingsStep(ScopedStateStore.Key<Boolean> keyCustomInstall,
ScopedStateStore.Key<Boolean> keyInstallHaxm,
ScopedStateStore.Key<Integer> keyEmulatorMemory) {
super("Emulator Settings");
myKeyCustomInstall = keyCustomInstall;
myKeyInstallHaxm = keyInstallHaxm;
myUnitLabel.setText(UI_UNITS.toString());
myKeyEmulatorMemory = keyEmulatorMemory;
final long memorySize = Haxm.getMemorySize();
myIntelHAXMDocumentationButton.setHyperlinkText("IntelĀ® HAXM Documentation");
myIntelHAXMDocumentationButton.setHyperlinkTarget(FirstRunWizardDefaults.HAXM_DOCUMENTATION_URL);
myRecommendedMemorySize = setupSliderAndSpinner(memorySize, myMemorySlider, myMemorySize);
setComponent(myRoot);
myRecommended.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
myState.put(myKeyEmulatorMemory, myRecommendedMemorySize);
}
});
}
@SuppressWarnings({"UseOfObsoleteCollectionType", "unchecked"})
private static int setupSliderAndSpinner(long memorySize, JSlider slider, JSpinner spinner) {
int recommendedMemorySize = FirstRunWizardDefaults.getRecommendedHaxmMemory(memorySize);
int maxMemory = Math.max(getMaxMemoryAllocation(memorySize), recommendedMemorySize);
int ticks = Math.min(maxMemory / MAX_TICK_RESOLUTION, MINOR_TICKS);
// Empty border is needed to avoid clipping long first and/or last label
slider.setBorder(BorderFactory.createEmptyBorder(0, 30, 0, 30));
slider.setMinimum(MIN_EMULATOR_MEMORY);
slider.setMaximum(maxMemory);
slider.setMinorTickSpacing(maxMemory / ticks);
slider.setMajorTickSpacing(maxMemory / MAJOR_TICKS);
Hashtable labels = new Hashtable();
int totalMemory = (int)(memorySize / UI_UNITS.getNumberOfBytes());
int labelSpacing = totalMemory / MAJOR_TICKS;
// Avoid overlapping
int minDistanceBetweenLabels = labelSpacing / 4;
for (int i = maxMemory; i >= labelSpacing; i -= labelSpacing) {
if (Math.abs(i - recommendedMemorySize) > minDistanceBetweenLabels) {
labels.put(i, new JLabel(getMemoryLabel(i)));
}
}
if (recommendedMemorySize > minDistanceBetweenLabels) {
labels.put(MIN_EMULATOR_MEMORY, new JLabel(getMemoryLabel(MIN_EMULATOR_MEMORY)));
}
labels.put(recommendedMemorySize, createRecommendedSizeLabel(recommendedMemorySize));
slider.setLabelTable(labels);
spinner.setModel(new SpinnerNumberModel(MIN_EMULATOR_MEMORY, MIN_EMULATOR_MEMORY, maxMemory, maxMemory / ticks));
return recommendedMemorySize;
}
private static JComponent createRecommendedSizeLabel(int memorySize) {
String labelText = String.format("<html><center>%s<br>(Recommended)<center></html>", getMemoryLabel(memorySize));
final Font boldLabelFont = UIUtil.getLabelFont().deriveFont(Font.BOLD);
// This is the only way as JSlider resets label font.
return new JLabel(labelText) {
@Override
public Font getFont() {
return boldLabelFont;
}
};
}
private static int getMaxMemoryAllocation(long memorySize) {
final long GB = Storage.Unit.GiB.getNumberOfBytes();
final long maxMemory;
if (memorySize > 4 * GB) {
maxMemory = memorySize - 2 * GB;
}
else {
maxMemory = memorySize / 2;
}
return (int)(maxMemory / UI_UNITS.getNumberOfBytes());
}
private static String getMemoryLabel(int memorySize) {
return new Storage(memorySize * UI_UNITS.getNumberOfBytes()).toString();
}
@Override
public boolean isStepVisible() {
return !SystemInfo.isLinux &&
Boolean.TRUE.equals(myState.get(myKeyCustomInstall)) &&
!Boolean.FALSE.equals(myState.get(myKeyInstallHaxm));
}
@Override
public void init() {
register(myKeyEmulatorMemory, myMemorySlider);
register(myKeyEmulatorMemory, myMemorySize, new SpinnerBinding());
}
@Nullable
@Override
public JLabel getMessageLabel() {
return null;
}
@Override
public JComponent getPreferredFocusedComponent() {
return myMemorySlider;
}
private static class SpinnerBinding extends ComponentBinding<Integer, JSpinner> {
@Override
public void setValue(@Nullable Integer newValue, @NotNull JSpinner component) {
component.setValue(newValue);
}
@Nullable
@Override
public Integer getValue(@NotNull JSpinner component) {
return (Integer)component.getValue();
}
@Override
public void addChangeListener(@NotNull ChangeListener listener, @NotNull JSpinner component) {
component.addChangeListener(listener);
}
}
}