| /* |
| * 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.dynamic; |
| |
| import com.android.tools.idea.wizard.WizardConstants; |
| import com.google.common.collect.Maps; |
| import com.intellij.ide.IdeBundle; |
| import com.intellij.openapi.application.Application; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.application.ModalityState; |
| import com.intellij.openapi.progress.ProgressIndicator; |
| import com.intellij.openapi.progress.ProgressManager; |
| import com.intellij.openapi.progress.Task; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.ui.DialogEarthquakeShaker; |
| import com.intellij.openapi.ui.DialogWrapper; |
| import com.intellij.openapi.util.SystemInfo; |
| import com.intellij.ui.components.panels.OpaquePanel; |
| import com.intellij.util.ui.UIUtil; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import javax.swing.border.EmptyBorder; |
| import java.awt.*; |
| import java.awt.event.ActionEvent; |
| import java.awt.image.BufferedImage; |
| import java.util.Map; |
| import java.util.concurrent.atomic.AtomicReference; |
| |
| /** |
| * Uses {@link DialogWrapper} to display a wizard in a dialog |
| */ |
| public class DialogWrapperHost extends DialogWrapper implements DynamicWizardHost { |
| // Action References. myCancelAction and myHelpAction are inherited |
| protected Action myPreviousAction = new PreviousAction(); |
| protected Action myNextAction = new NextAction(); |
| protected Action myFinishAction = new FinishAction(); |
| |
| private DynamicWizard myWizard; |
| private TallImageComponent myIcon = new TallImageComponent(null); |
| private AtomicReference<ProgressIndicator> myCurrentProgressIndicator = new AtomicReference<ProgressIndicator>(); |
| |
| private JPanel myCenterPanel; |
| |
| // UI references |
| private Map<Action, JButton> myActionToButtonMap = Maps.newHashMapWithExpectedSize(5); |
| |
| public DialogWrapperHost(@Nullable Project project, IdeModalityType modalityType) { |
| super(project, true, modalityType); |
| } |
| |
| public DialogWrapperHost(@Nullable Project project) { |
| super(project); |
| } |
| |
| @Override |
| public void init(@NotNull DynamicWizard wizard) { |
| Window window = getWindow(); |
| if (window == null) { |
| assert ApplicationManager.getApplication().isUnitTestMode(); |
| } else { |
| window.setPreferredSize(WizardConstants.DEFAULT_WIZARD_WINDOW_SIZE); |
| } |
| myWizard = wizard; |
| super.init(); |
| // Clear out the large border |
| Container centerRootPanel = myCenterPanel.getParent(); |
| if (centerRootPanel != null) { |
| Container rootPanel = centerRootPanel.getParent(); |
| if (rootPanel instanceof JPanel) { |
| // Clear out default borders, we'll set our own later |
| ((JPanel)rootPanel).setBorder(new EmptyBorder(0, 0, 0, 0)); |
| } |
| } |
| } |
| |
| @Override |
| public void close(@NotNull CloseAction action) { |
| if (action == CloseAction.FINISH) { |
| doOKAction(); |
| } |
| else if (action == CloseAction.CANCEL) { |
| dialogWrapperCancel(); |
| } |
| } |
| |
| private void dialogWrapperCancel() { |
| super.doCancelAction(); |
| } |
| |
| @Nullable |
| @Override |
| public JComponent getPreferredFocusedComponent() { |
| JComponent focusedComponent = myWizard.getPreferredFocusedComponent(); |
| return focusedComponent == null ? getNextButton() : focusedComponent; |
| } |
| |
| @Override |
| public void shakeWindow() { |
| if (!ApplicationManager.getApplication().isUnitTestMode()) { |
| DialogEarthquakeShaker.shake((JDialog)getPeer().getWindow()); |
| } |
| } |
| |
| /** |
| * ACTIONS |
| */ |
| @NotNull |
| @Override |
| protected Action[] createActions() { |
| if (getHelpId() == null) { |
| if (SystemInfo.isMac) { |
| return new Action[]{getCancelAction(), myPreviousAction, myNextAction, myFinishAction}; |
| } |
| |
| return new Action[]{myPreviousAction, myNextAction, getCancelAction(), myFinishAction}; |
| } |
| else { |
| if (SystemInfo.isMac) { |
| return new Action[]{getHelpAction(), getCancelAction(), myPreviousAction, myNextAction, myFinishAction}; |
| } |
| return new Action[]{myPreviousAction, myNextAction, getCancelAction(), myFinishAction, getHelpAction()}; |
| } |
| } |
| |
| @Override |
| public void setIcon(@Nullable Icon icon) { |
| myIcon.setIcon(icon); |
| myIcon.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 8)); |
| } |
| |
| @Override |
| public void runSensitiveOperation(@NotNull final 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 = getRootPane(); |
| rootPane.setDefaultButton(null); |
| updateButtons(false, false, true, false); |
| |
| Task.Backgroundable task = new Task.Backgroundable(null, myWizard.getWizardActionDescription(), cancellable) { |
| @Override |
| public void run(@NotNull ProgressIndicator indicator) { |
| operation.run(); |
| // this can't be done in onSuccess because the ModalityState needs to be set |
| ApplicationManager.getApplication().invokeAndWait(new Runnable() { |
| @Override |
| public void run() { |
| updateButtons(false, false, false, true); |
| myCurrentProgressIndicator.set(null); |
| } |
| }, ModalityState.stateForComponent(myWizard.getContentPane())); |
| } |
| }; |
| ProgressManager.getInstance().runProcessWithProgressAsynchronously(task, progressIndicator); |
| } |
| |
| @Override |
| protected final JButton createJButtonForAction(Action action) { |
| // Save a reference to the created button so that we can access them |
| // later to enable/disable |
| JButton button = super.createJButtonForAction(action); |
| myActionToButtonMap.put(action, button); |
| return button; |
| } |
| |
| @Override |
| protected void doHelpAction() { |
| // TODO: Implement |
| } |
| |
| /** |
| * Create the south panel that serves as the container for the wizard buttons. |
| * The base class does all the heavy lifting already, we just adjust the margins here. |
| */ |
| @Nullable |
| @Override |
| protected JComponent createSouthPanel() { |
| JPanel southPanel = (JPanel)super.createSouthPanel(); |
| assert southPanel != null; |
| southPanel.setBorder(new EmptyBorder(WizardConstants.STUDIO_WIZARD_INSETS)); |
| return southPanel; |
| } |
| |
| @Override |
| public final void doCancelAction() { |
| ProgressIndicator indicator = myCurrentProgressIndicator.get(); |
| if (indicator != null) { |
| indicator.cancel(); |
| } else { |
| myWizard.doCancelAction(); |
| } |
| } |
| |
| /** |
| * Update the buttons for the wizard |
| * @param canGoPrev whether the previous button is enabled |
| * @param canGoNext whether the next button is enabled |
| * @param canCancel whether the cancel button is enabled |
| * @param canFinish if this is set to true and the current path is the last non-optional path, the canFinish |
| */ |
| @Override |
| public void updateButtons(boolean canGoPrev, boolean canGoNext, boolean canCancel, boolean canFinish) { |
| JButton prev = getPreviousButton(); |
| if (prev != null) { |
| prev.setEnabled(canGoPrev); |
| } |
| JButton next = getNextButton(); |
| if (next != null) { |
| next.setEnabled(canGoNext); |
| } |
| |
| getFinishButton().setEnabled(canFinish); |
| getCancelButton().setEnabled(canCancel); |
| if (!ApplicationManager.getApplication().isUnitTestMode()) { |
| getRootPane().setDefaultButton(canFinish ? getFinishButton() : getNextButton()); |
| } |
| } |
| |
| /** |
| * Create the center panel that will serve as a container for the UI components of each step. |
| */ |
| @Override |
| @Nullable |
| protected final JComponent createCenterPanel() { |
| myCenterPanel = new JPanel(new BorderLayout()); |
| myCenterPanel.add(myWizard.getContentPane(), BorderLayout.CENTER); |
| myCenterPanel.add(myIcon, BorderLayout.WEST); |
| return myCenterPanel; |
| } |
| |
| @Nullable |
| protected JButton getNextButton() { |
| return myActionToButtonMap.get(myNextAction); |
| } |
| |
| @Nullable |
| protected JButton getPreviousButton() { |
| return myActionToButtonMap.get(myPreviousAction); |
| } |
| |
| @NotNull |
| protected JButton getFinishButton() { |
| return myActionToButtonMap.get(myFinishAction); |
| } |
| |
| @NotNull |
| protected JButton getCancelButton() { |
| return myActionToButtonMap.get(myCancelAction); |
| } |
| |
| @Override |
| public void setTitle(String title) { |
| super.setTitle(title); |
| } |
| |
| @Override |
| public void setPreferredWindowSize(Dimension dimension) { |
| getContentPanel().setPreferredSize(dimension); |
| } |
| |
| /** |
| * Taken from {@link com.intellij.ide.wizard.AbstractWizard} |
| */ |
| public static class TallImageComponent extends OpaquePanel { |
| @Nullable private Icon myIcon; |
| |
| public TallImageComponent(@Nullable Icon icon) { |
| myIcon = icon; |
| } |
| |
| @Override |
| protected void paintChildren(Graphics g) { |
| if (myIcon == null) return; |
| |
| paintIcon(g); |
| } |
| |
| public void paintIcon(Graphics g) { |
| if (myIcon == null) { |
| return; |
| } |
| final BufferedImage image = UIUtil.createImage(myIcon.getIconWidth(), myIcon.getIconHeight(), BufferedImage.TYPE_INT_ARGB); |
| final Graphics2D gg = image.createGraphics(); |
| myIcon.paintIcon(this, gg, 0, 0); |
| |
| final Rectangle bounds = g.getClipBounds(); |
| int y = myIcon.getIconHeight() - 1; |
| while (y < bounds.y + bounds.height) { |
| g.drawImage(image, bounds.x, y, bounds.x + bounds.width, y + 1, 0, myIcon.getIconHeight() - 1, bounds.width, myIcon.getIconHeight(), |
| this); |
| |
| y++; |
| } |
| |
| |
| g.drawImage(image, 0, 0, this); |
| } |
| |
| public void setIcon(@Nullable Icon icon) { |
| myIcon = icon; |
| revalidate(); |
| repaint(); |
| } |
| |
| @Override |
| public Dimension getPreferredSize() { |
| return new Dimension(myIcon != null ? myIcon.getIconWidth() : 0, 0); |
| } |
| |
| @Override |
| public Dimension getMinimumSize() { |
| return new Dimension(myIcon != null ? myIcon.getIconWidth() : 0, 0); |
| } |
| } |
| |
| protected class NextAction extends DialogWrapperAction { |
| protected NextAction() { |
| super(IdeBundle.message("button.wizard.next")); |
| putValue(DEFAULT_ACTION, Boolean.TRUE); |
| } |
| |
| @Override |
| protected void doAction(ActionEvent e) { |
| myWizard.doNextAction(); |
| } |
| } |
| |
| protected class PreviousAction extends DialogWrapperAction { |
| protected PreviousAction() { |
| super(IdeBundle.message("button.wizard.previous")); |
| } |
| |
| @Override |
| protected void doAction(ActionEvent e) { |
| myWizard.doPreviousAction(); |
| } |
| } |
| |
| protected class FinishAction extends DialogWrapperAction { |
| protected FinishAction() { |
| super(IdeBundle.message("button.finish")); |
| } |
| |
| @Override |
| protected void doAction(ActionEvent e) { |
| myWizard.doFinishAction(); |
| } |
| } |
| } |