blob: 04ef4ce9bba17abc08a3cd40aeeff8c3a69728d3 [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.repository.descriptors.PkgType;
import com.android.tools.idea.sdk.remote.RemotePkgInfo;
import com.android.tools.idea.welcome.config.FirstRunWizardMode;
import com.android.tools.idea.wizard.dynamic.DynamicWizard;
import com.android.tools.idea.wizard.dynamic.DynamicWizardHost;
import com.android.tools.idea.wizard.WizardConstants;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.util.concurrent.Atomics;
import com.intellij.ide.IdeBundle;
import com.intellij.ide.ui.UISettings;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.ui.OptionAction;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.wm.WelcomeScreen;
import com.intellij.openapi.wm.impl.welcomeScreen.NewWelcomeScreen;
import com.intellij.ui.IdeBorderFactory;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
/**
* Hosts dynamic wizards in the welcome frame.
*/
public class FirstRunWizardHost extends JPanel implements WelcomeScreen, DynamicWizardHost {
private static final Insets BUTTON_MARGINS = new Insets(2, 16, 2, 16);
@NotNull private final FirstRunWizardMode myMode;
@NotNull private final Multimap<PkgType, RemotePkgInfo> myRemotePackages;
private Action myCancelAction = new CancelAction();
private Action myPreviousAction = new PreviousAction();
private Action myNextAction = new NextAction();
private FinishAction myFinishAction = new FinishAction();
private FirstRunWizard myWizard;
private JFrame myFrame;
private String myTitle;
private Dimension myPreferredWindowSize = new Dimension(800, 600);
private Map<Action, JButton> myActionToButtonMap = Maps.newHashMap();
private AtomicReference<ProgressIndicator> myCurrentProgressIndicator = Atomics.newReference();
private boolean myIsActive;
public FirstRunWizardHost(@NotNull FirstRunWizardMode mode,
@NotNull Multimap<PkgType, RemotePkgInfo> remotePackages) {
super(new BorderLayout());
myMode = mode;
myRemotePackages = remotePackages;
add(createSouthPanel(), BorderLayout.SOUTH);
}
private static void setMargin(JButton button) {
// Aqua LnF does a good job of setting proper margin between buttons. Setting them specifically causes them be 'square' style instead of
// 'rounded', which is expected by apple users.
if (!SystemInfo.isMac && BUTTON_MARGINS != null) {
button.setMargin(BUTTON_MARGINS);
}
}
@Override
public JComponent getWelcomePanel() {
if (myWizard == null) {
setupWizard();
}
assert myWizard != null;
return this;
}
private void setupWizard() {
DynamicWizard wizard = new FirstRunWizard(this, myMode, myRemotePackages);
wizard.init();
add(wizard.getContentPane(), BorderLayout.CENTER);
}
@Override
public void setupFrame(JFrame frame) {
myFrame = frame;
if (myTitle != null) {
frame.setTitle(myTitle);
}
frame.setSize(myPreferredWindowSize);
Dimension size = Toolkit.getDefaultToolkit().getScreenSize();
int x = (size.width - myPreferredWindowSize.width) / 2;
int y = (size.height - myPreferredWindowSize.height) / 2;
frame.setLocation(x, y);
JButton defaultButton = myActionToButtonMap.get(myFinishAction);
if (!defaultButton.isEnabled()) {
defaultButton = myActionToButtonMap.get(myNextAction);
}
setDefaultButton(defaultButton);
myIsActive = true;
WelcomeScreenWindowListener.install(frame, this);
}
@Override
public void dispose() {
// Nothing
}
@NotNull
@Override
public Disposable getDisposable() {
return this;
}
@Override
public void init(@NotNull DynamicWizard wizard) {
myWizard = (FirstRunWizard)wizard;
}
@Override
public boolean showAndGet() {
return false;
}
@Override
public void close(@NotNull CloseAction action) {
myIsActive = false;
if (action == CloseAction.FINISH || action == CloseAction.CANCEL) {
setDefaultButton(null);
NewWelcomeScreen welcomeScreen = new NewWelcomeScreen();
Disposer.register(getDisposable(), welcomeScreen);
myFrame.setContentPane(welcomeScreen.getWelcomePanel());
welcomeScreen.setupFrame(myFrame);
}
else if (action == CloseAction.EXIT) {
myFrame.setVisible(false);
myFrame.dispose();
ApplicationManager.getApplication().exit();
}
}
@Override
public void shakeWindow() {
// Do nothing
}
@Override
public void updateButtons(boolean canGoPrev, boolean canGoNext, boolean canCancel, boolean canFinish) {
setButtonEnabled(myCancelAction, canCancel);
setButtonEnabled(myPreviousAction, canGoPrev);
setButtonEnabled(myNextAction, canGoNext);
setButtonEnabled(myFinishAction, canFinish);
if (myFrame != null) {
setDefaultButton(myActionToButtonMap.get(canFinish ? myFinishAction : myNextAction));
}
}
private void setButtonEnabled(Action action, boolean enabled) {
JButton button = myActionToButtonMap.get(action);
if (button != null) {
button.setEnabled(enabled);
}
}
@Override
public void setTitle(String title) {
myTitle = title;
if (myFrame != null) {
myFrame.setTitle(title);
}
}
@Override
public void setIcon(@Nullable Icon icon) {
}
@Override
public void runSensitiveOperation(@NotNull ProgressIndicator progressIndicator, boolean cancellable, @NotNull final Runnable operation) {
final Application application = ApplicationManager.getApplication();
application.assertIsDispatchThread();
if (!myCurrentProgressIndicator.compareAndSet(null, progressIndicator)) {
throw new IllegalStateException("Submitting an operation while another is in progress.");
}
final JRootPane rootPane = myFrame.getRootPane();
final JButton defaultButton = rootPane.getDefaultButton();
rootPane.setDefaultButton(null);
updateButtons(false, false, true, false);
Task.Backgroundable task = new LongRunningOperationWrapper(operation, cancellable, defaultButton);
ProgressManager.getInstance().runProcessWithProgressAsynchronously(task, progressIndicator);
}
/**
* Creates panel located at the south of the content pane. By default that
* panel contains dialog's buttons. This default implementation uses <code>createActions()</code>
* and <code>createJButtonForAction(Action)</code> methods to construct the panel.
*
* @return south panel
*/
@NotNull
private JComponent createSouthPanel() {
Action[] actions = createActions();
List<JButton> buttons = new ArrayList<JButton>();
JPanel panel = new JPanel(new BorderLayout());
final JPanel lrButtonsPanel = new JPanel(new GridBagLayout());
final Insets insets = SystemInfo.isMacOSLeopard ? new Insets(0, 0, 0, 0) : new Insets(8, 0, 0, 0);
if (actions.length > 0) {
int gridX = 0;
lrButtonsPanel.add(Box.createHorizontalGlue(), // left strut
new GridBagConstraints(gridX++, 0, 1, 1, 1, 0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, insets, 0,
0));
if (actions.length > 0) {
JPanel buttonsPanel = createButtons(actions, buttons);
lrButtonsPanel.add(buttonsPanel,
new GridBagConstraints(gridX, 0, 1, 1, 0, 0, GridBagConstraints.CENTER, GridBagConstraints.NONE, insets, 0, 0));
}
}
panel.add(lrButtonsPanel, BorderLayout.CENTER);
panel.setBorder(IdeBorderFactory.createEmptyBorder(WizardConstants.STUDIO_WIZARD_INSETS));
return panel;
}
private Action[] createActions() {
if (SystemInfo.isMac) {
return new Action[]{myCancelAction, myPreviousAction, myNextAction, myFinishAction};
}
else {
return new Action[]{myPreviousAction, myNextAction, myCancelAction, myFinishAction};
}
}
@NotNull
private JPanel createButtons(@NotNull Action[] actions, @NotNull List<JButton> buttons) {
if (!UISettings.getShadowInstance().ALLOW_MERGE_BUTTONS) {
final List<Action> actionList = new ArrayList<Action>();
for (Action action : actions) {
actionList.add(action);
if (action instanceof OptionAction) {
final Action[] options = ((OptionAction)action).getOptions();
actionList.addAll(Arrays.asList(options));
}
}
if (actionList.size() != actions.length) {
actions = actionList.toArray(actionList.toArray(new Action[actionList.size()]));
}
}
JPanel buttonsPanel = new JPanel(new GridLayout(1, actions.length, SystemInfo.isMacOSLeopard ? 0 : 5, 0));
for (final Action action : actions) {
JButton button = createJButtonForAction(action);
final Object value = action.getValue(Action.MNEMONIC_KEY);
if (value instanceof Integer) {
final int mnemonic = ((Integer)value).intValue();
button.setMnemonic(mnemonic);
}
buttons.add(button);
buttonsPanel.add(button);
}
return buttonsPanel;
}
/**
* Creates <code>JButton</code> for the specified action. If the button has not <code>null</code>
* value for <code>DialogWrapper.DEFAULT_ACTION</code> key then the created button will be the
* default one for the dialog.
*
* @param action action for the button
* @return button with action specified
* @see com.intellij.openapi.ui.DialogWrapper#DEFAULT_ACTION
*/
protected JButton createJButtonForAction(Action action) {
JButton button = new JButton(action);
String text = button.getText();
if (SystemInfo.isMac) {
button.putClientProperty("JButton.buttonType", "text");
}
if (text != null) {
int mnemonic = 0;
StringBuilder plainText = new StringBuilder();
for (int i = 0; i < text.length(); i++) {
char ch = text.charAt(i);
if (ch == '_' || ch == '&') {
if (i >= text.length()) {
break;
}
ch = text.charAt(i);
if (ch != '_' && ch != '&') {
// Mnemonic is case insensitive.
int vk = ch;
if (vk >= 'a' && vk <= 'z') {
vk -= 'a' - 'A';
}
mnemonic = vk;
}
}
plainText.append(ch);
}
button.setText(plainText.toString());
button.setMnemonic(mnemonic);
setMargin(button);
}
myActionToButtonMap.put(action, button);
return button;
}
@Override
public void setPreferredWindowSize(Dimension preferredWindowSize) {
myPreferredWindowSize = preferredWindowSize;
if (myFrame != null) {
myFrame.setSize(preferredWindowSize);
}
}
private void setDefaultButton(@Nullable JButton button) {
JRootPane rootPane = getRootPane();
if (rootPane != null) {
rootPane.setDefaultButton(button);
}
}
public boolean isActive() {
return myIsActive;
}
/**
* Cancels the wizard.
*/
public void cancel() {
ProgressIndicator indicator = myCurrentProgressIndicator.get();
if (indicator == null) {
myWizard.doCancelAction();
}
else {
indicator.cancel();
JButton button = myActionToButtonMap.get(myCancelAction);
if (button != null) {
button.setEnabled(false);
}
}
}
protected class CancelAction extends AbstractAction {
protected CancelAction() {
putValue(NAME, IdeBundle.message("button.cancel"));
}
@Override
public void actionPerformed(ActionEvent e) {
cancel();
}
}
protected class NextAction extends AbstractAction {
protected NextAction() {
putValue(NAME, IdeBundle.message("button.wizard.next"));
}
@Override
public void actionPerformed(ActionEvent e) {
myWizard.doNextAction();
}
}
protected class PreviousAction extends AbstractAction {
protected PreviousAction() {
putValue(NAME, IdeBundle.message("button.wizard.previous"));
}
@Override
public void actionPerformed(ActionEvent e) {
myWizard.doPreviousAction();
}
}
protected class FinishAction extends AbstractAction {
protected FinishAction() {
putValue(NAME, IdeBundle.message("button.finish"));
}
@Override
public void actionPerformed(ActionEvent e) {
myWizard.doFinishAction();
}
}
private class LongRunningOperationWrapper extends Task.Backgroundable {
private final Runnable myOperation;
private final JButton myDefaultButton;
public LongRunningOperationWrapper(Runnable operation, boolean cancellable, JButton defaultButton) {
super(null, FirstRunWizardHost.this.myWizard.getWizardActionDescription(), cancellable);
myOperation = operation;
myDefaultButton = defaultButton;
}
@Override
public void onSuccess() {
myCurrentProgressIndicator.set(null);
updateButtons(false, false, false, true);
myFrame.getRootPane().setDefaultButton(myDefaultButton);
}
@Override
public void run(@NotNull ProgressIndicator indicator) {
myOperation.run();
}
@Override
public void onCancel() {
onSuccess();
}
}
}