| /* |
| * Copyright 2000-2014 JetBrains s.r.o. |
| * |
| * 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.intellij.ide.wizard; |
| |
| import com.intellij.CommonBundle; |
| import com.intellij.ide.IdeBundle; |
| import com.intellij.openapi.application.ApplicationInfo; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.help.HelpManager; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.ui.DialogWrapper; |
| import com.intellij.openapi.ui.Messages; |
| import com.intellij.openapi.util.SystemInfo; |
| import com.intellij.openapi.wm.IdeFocusManager; |
| import com.intellij.ui.JBCardLayout; |
| import com.intellij.ui.components.panels.OpaquePanel; |
| import com.intellij.util.containers.ContainerUtil; |
| import com.intellij.util.ui.UIUtil; |
| import com.intellij.util.ui.update.UiNotifyConnector; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import java.awt.*; |
| import java.awt.event.ActionEvent; |
| import java.awt.event.ActionListener; |
| import java.awt.event.KeyEvent; |
| import java.awt.image.BufferedImage; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| public abstract class AbstractWizard<T extends Step> extends DialogWrapper { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.ide.wizard.AbstractWizard"); |
| |
| protected int myCurrentStep; |
| protected final ArrayList<T> mySteps; |
| private JButton myPreviousButton; |
| private JButton myNextButton; |
| private JButton myCancelButton; |
| private JButton myHelpButton; |
| protected JPanel myContentPanel; |
| protected TallImageComponent myIcon; |
| private Component myCurrentStepComponent; |
| private final Map<Component, String> myComponentToIdMap = new HashMap<Component, String>(); |
| private final StepListener myStepListener = new StepListener() { |
| public void stateChanged() { |
| updateStep(); |
| } |
| }; |
| |
| public AbstractWizard(final String title, final Component dialogParent) { |
| super(dialogParent, true); |
| mySteps = new ArrayList<T>(); |
| initWizard(title); |
| } |
| |
| public AbstractWizard(final String title, @Nullable final Project project) { |
| super(project, true); |
| mySteps = new ArrayList<T>(); |
| initWizard(title); |
| } |
| |
| private void initWizard(final String title) { |
| setTitle(title); |
| myCurrentStep = 0; |
| myPreviousButton = new JButton(IdeBundle.message("button.wizard.previous")); |
| myNextButton = new JButton(IdeBundle.message("button.wizard.next")); |
| myCancelButton = new JButton(CommonBundle.getCancelButtonText()); |
| myHelpButton = new JButton(CommonBundle.getHelpButtonText()); |
| myContentPanel = new JPanel(new JBCardLayout()); |
| |
| myIcon = new TallImageComponent(null); |
| |
| JRootPane rootPane = getRootPane(); |
| if (rootPane != null) { // it will be null in headless mode, i.e. tests |
| rootPane.registerKeyboardAction( |
| new ActionListener() { |
| public void actionPerformed(final ActionEvent e) { |
| helpAction(); |
| } |
| }, |
| KeyStroke.getKeyStroke(KeyEvent.VK_F1, 0), |
| JComponent.WHEN_IN_FOCUSED_WINDOW |
| ); |
| |
| rootPane.registerKeyboardAction( |
| new ActionListener() { |
| public void actionPerformed(final ActionEvent e) { |
| helpAction(); |
| } |
| }, |
| KeyStroke.getKeyStroke(KeyEvent.VK_HELP, 0), |
| JComponent.WHEN_IN_FOCUSED_WINDOW |
| ); |
| } |
| } |
| |
| protected JComponent createSouthPanel() { |
| JPanel panel = new JPanel(new BorderLayout()); |
| panel.setBorder(BorderFactory.createEmptyBorder(8, 0, 0, 0)); |
| |
| JPanel buttonPanel = new JPanel(); |
| |
| if (SystemInfo.isMac) { |
| panel.add(buttonPanel, BorderLayout.EAST); |
| buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS)); |
| |
| if (!UIUtil.isUnderDarcula()) { |
| myHelpButton.putClientProperty("JButton.buttonType", "help"); |
| } |
| if (UIUtil.isUnderAquaLookAndFeel()) { |
| myHelpButton.setText(""); |
| } |
| |
| JPanel leftPanel = new JPanel(); |
| if (ApplicationInfo.contextHelpAvailable()) { |
| leftPanel.add(myHelpButton); |
| } |
| leftPanel.add(myCancelButton); |
| panel.add(leftPanel, BorderLayout.WEST); |
| |
| if (mySteps.size() > 1) { |
| buttonPanel.add(Box.createHorizontalStrut(5)); |
| buttonPanel.add(myPreviousButton); |
| } |
| buttonPanel.add(Box.createHorizontalStrut(5)); |
| buttonPanel.add(myNextButton); |
| } |
| else { |
| panel.add(buttonPanel, BorderLayout.CENTER); |
| GroupLayout layout = new GroupLayout(buttonPanel); |
| buttonPanel.setLayout(layout); |
| layout.setAutoCreateGaps(true); |
| |
| final GroupLayout.SequentialGroup hGroup = layout.createSequentialGroup(); |
| final GroupLayout.ParallelGroup vGroup = layout.createParallelGroup(); |
| final Collection<Component> buttons = ContainerUtil.newArrayListWithCapacity(5); |
| final boolean helpAvailable = ApplicationInfo.contextHelpAvailable(); |
| |
| if (helpAvailable && UIUtil.isUnderGTKLookAndFeel()) { |
| add(hGroup, vGroup, buttons, myHelpButton); |
| } |
| add(hGroup, vGroup, null, Box.createHorizontalGlue()); |
| if (mySteps.size() > 1) { |
| add(hGroup, vGroup, buttons, myPreviousButton); |
| } |
| add(hGroup, vGroup, buttons, myNextButton, myCancelButton); |
| if (helpAvailable && !UIUtil.isUnderGTKLookAndFeel()) { |
| add(hGroup, vGroup, buttons, myHelpButton); |
| } |
| |
| layout.setHorizontalGroup(hGroup); |
| layout.setVerticalGroup(vGroup); |
| layout.linkSize(buttons.toArray(new Component[buttons.size()])); |
| } |
| |
| myPreviousButton.setEnabled(false); |
| myPreviousButton.addActionListener(new ActionListener() { |
| public void actionPerformed(final ActionEvent e) { |
| doPreviousAction(); |
| } |
| }); |
| myNextButton.addActionListener(new ActionListener() { |
| public void actionPerformed(final ActionEvent e) { |
| if (isLastStep()) { |
| // Commit data of current step and perform OK action |
| final Step currentStep = mySteps.get(myCurrentStep); |
| LOG.assertTrue(currentStep != null); |
| try { |
| currentStep._commit(true); |
| doOKAction(); |
| } |
| catch (final CommitStepException exc) { |
| String message = exc.getMessage(); |
| if (message != null) { |
| Messages.showErrorDialog(myContentPanel, message); |
| } |
| } |
| } |
| else { |
| doNextAction(); |
| } |
| } |
| }); |
| |
| myCancelButton.addActionListener( |
| new ActionListener() { |
| public void actionPerformed(final ActionEvent e) { |
| doCancelAction(); |
| } |
| } |
| ); |
| myHelpButton.addActionListener(new ActionListener() { |
| public void actionPerformed(final ActionEvent e) { |
| helpAction(); |
| } |
| }); |
| |
| return panel; |
| } |
| |
| private static void add(final GroupLayout.Group hGroup, |
| final GroupLayout.Group vGroup, |
| @Nullable final Collection<Component> collection, |
| final Component... components) { |
| for (Component component : components) { |
| hGroup.addComponent(component); |
| vGroup.addComponent(component); |
| if (collection != null) collection.add(component); |
| } |
| } |
| |
| public static class TallImageComponent extends OpaquePanel { |
| private Icon myIcon; |
| |
| private TallImageComponent(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(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 JComponent createCenterPanel() { |
| final JPanel panel = new JPanel(new BorderLayout()); |
| panel.add(myContentPanel, BorderLayout.CENTER); |
| panel.add(myIcon, BorderLayout.WEST); |
| return panel; |
| } |
| |
| public int getCurrentStep() { |
| return myCurrentStep; |
| } |
| |
| public int getStepCount() { |
| return mySteps.size(); |
| } |
| |
| public T getCurrentStepObject() { |
| return mySteps.get(myCurrentStep); |
| } |
| |
| public void addStep(@NotNull final T step) { |
| addStep(step, mySteps.size()); |
| } |
| |
| public void addStep(@NotNull final T step, int index) { |
| mySteps.add(index, step); |
| |
| if (step instanceof StepAdapter) { |
| ((StepAdapter)step).registerStepListener(myStepListener); |
| } |
| // card layout is used |
| final Component component = step.getComponent(); |
| if (component != null) { |
| addStepComponent(component); |
| } |
| } |
| |
| protected void init() { |
| super.init(); |
| updateStep(); |
| } |
| |
| |
| protected String addStepComponent(final Component component) { |
| String id = myComponentToIdMap.get(component); |
| if (id == null) { |
| id = Integer.toString(myComponentToIdMap.size()); |
| myComponentToIdMap.put(component, id); |
| myContentPanel.add(component, id); |
| } |
| return id; |
| } |
| |
| private void showStepComponent(final Component component) { |
| String id = myComponentToIdMap.get(component); |
| if (id == null) { |
| id = addStepComponent(component); |
| myContentPanel.revalidate(); |
| myContentPanel.repaint(); |
| } |
| ((JBCardLayout)myContentPanel.getLayout()).swipe(myContentPanel, id, JBCardLayout.SwipeDirection.AUTO); |
| } |
| |
| protected void doPreviousAction() { |
| // Commit data of current step |
| final Step currentStep = mySteps.get(myCurrentStep); |
| LOG.assertTrue(currentStep != null); |
| try { |
| currentStep._commit(false); |
| } |
| catch (final CommitStepException exc) { |
| Messages.showErrorDialog( |
| myContentPanel, |
| exc.getMessage() |
| ); |
| return; |
| } |
| |
| myCurrentStep = getPreviousStep(myCurrentStep); |
| updateStep(); |
| } |
| |
| protected void doNextAction() { |
| // Commit data of current step |
| final Step currentStep = mySteps.get(myCurrentStep); |
| LOG.assertTrue(currentStep != null); |
| LOG.assertTrue(!isLastStep(), "steps: " + mySteps + " current: " + currentStep); |
| try { |
| currentStep._commit(false); |
| } |
| catch (final CommitStepException exc) { |
| Messages.showErrorDialog( |
| myContentPanel, |
| exc.getMessage() |
| ); |
| return; |
| } |
| |
| myCurrentStep = getNextStep(myCurrentStep); |
| updateStep(); |
| } |
| |
| /** |
| * override this to provide alternate step order |
| * @param step index |
| * @return the next step's index |
| */ |
| protected int getNextStep(int step) { |
| final int stepCount = mySteps.size(); |
| if (++step >= stepCount) { |
| step = stepCount - 1; |
| } |
| return step; |
| } |
| |
| protected final int getNextStep() { |
| return getNextStep(getCurrentStep()); |
| } |
| |
| protected T getNextStepObject() { |
| int step = getNextStep(); |
| return mySteps.get(step); |
| } |
| |
| /** |
| * override this to provide alternate step order |
| * @param step index |
| * @return the previous step's index |
| */ |
| protected int getPreviousStep(int step) { |
| if (--step < 0) { |
| step = 0; |
| } |
| return step; |
| } |
| |
| protected final int getPreviousStep() { |
| return getPreviousStep(getCurrentStep()); |
| } |
| |
| protected void updateStep() { |
| if (mySteps.isEmpty()) { |
| return; |
| } |
| |
| final Step step = mySteps.get(myCurrentStep); |
| LOG.assertTrue(step != null); |
| step._init(); |
| myCurrentStepComponent = step.getComponent(); |
| LOG.assertTrue(myCurrentStepComponent != null); |
| showStepComponent(myCurrentStepComponent); |
| |
| Icon icon = step.getIcon(); |
| if (icon != null) { |
| myIcon.setIcon(icon); |
| myIcon.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 8)); |
| } |
| |
| updateButtons(); |
| |
| JComponent component = mySteps.get(getCurrentStep()).getPreferredFocusedComponent(); |
| requestFocusTo(component != null ? component : myNextButton); |
| } |
| |
| private static void requestFocusTo(final JComponent component) { |
| UiNotifyConnector.doWhenFirstShown(component, new Runnable() { |
| @Override |
| public void run() { |
| final IdeFocusManager focusManager = IdeFocusManager.findInstanceByComponent(component); |
| focusManager.requestFocus(component, false); |
| } |
| }); |
| } |
| |
| @Nullable |
| @Override |
| public JComponent getPreferredFocusedComponent() { |
| JComponent component = getCurrentStepObject().getPreferredFocusedComponent(); |
| return component == null ? super.getPreferredFocusedComponent() : component; |
| } |
| |
| protected boolean canGoNext() { |
| return true; |
| } |
| |
| protected boolean canFinish() { |
| return isLastStep() && canGoNext(); |
| } |
| |
| protected void updateButtons() { |
| if (isLastStep()) { |
| if (mySteps.size() > 1) { |
| myNextButton.setText(UIUtil.removeMnemonic(IdeBundle.message("button.finish"))); |
| myNextButton.setMnemonic('F'); |
| } |
| else { |
| myNextButton.setText(IdeBundle.message("button.ok")); |
| } |
| myNextButton.setEnabled(canFinish()); |
| } |
| else { |
| myNextButton.setText(UIUtil.removeMnemonic(IdeBundle.message("button.wizard.next"))); |
| myNextButton.setMnemonic('N'); |
| myNextButton.setEnabled(canGoNext()); |
| } |
| |
| if (myNextButton.isEnabled() && !ApplicationManager.getApplication().isUnitTestMode()) { |
| getRootPane().setDefaultButton(myNextButton); |
| } |
| |
| myPreviousButton.setEnabled(myCurrentStep > 0); |
| } |
| |
| protected boolean isLastStep() { |
| return myCurrentStep == mySteps.size() - 1 || getCurrentStep() == getNextStep(getCurrentStep()); |
| } |
| |
| protected JButton getNextButton() { |
| return myNextButton; |
| } |
| |
| protected JButton getPreviousButton() { |
| return myPreviousButton; |
| } |
| |
| protected JButton getHelpButton() { |
| return myHelpButton; |
| } |
| |
| public JButton getCancelButton() { |
| return myCancelButton; |
| } |
| |
| @Deprecated |
| protected JButton getFinishButton() { |
| return new JButton(); |
| } |
| |
| public Component getCurrentStepComponent() { |
| return myCurrentStepComponent; |
| } |
| |
| protected void helpAction() { |
| HelpManager.getInstance().invokeHelp(getHelpID()); |
| } |
| |
| @Override |
| protected void doHelpAction() { |
| HelpManager.getInstance().invokeHelp(getHelpID()); |
| } |
| |
| protected int getNumberOfSteps() { |
| return mySteps.size(); |
| } |
| |
| @Nullable |
| @NonNls |
| protected abstract String getHelpID(); |
| } |