bdd19061591b41188c61085392503b5e5234ca3f
[idea/community.git] / platform / platform-impl / src / com / intellij / ide / customize / CustomizeIDEWizardDialog.java
1 // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.ide.customize;
3
4 import com.intellij.ide.IdeBundle;
5 import com.intellij.ide.startup.StartupActionScriptManager;
6 import com.intellij.idea.SplashManager;
7 import com.intellij.idea.StartupUtil;
8 import com.intellij.openapi.application.ApplicationNamesInfo;
9 import com.intellij.openapi.ui.DialogWrapper;
10 import com.intellij.openapi.util.text.HtmlBuilder;
11 import com.intellij.openapi.util.text.HtmlChunk;
12 import com.intellij.openapi.util.text.StringUtil;
13 import com.intellij.openapi.wm.IdeFocusManager;
14 import com.intellij.ui.JBCardLayout;
15 import com.intellij.ui.components.JBLabel;
16 import com.intellij.util.ui.JBUI;
17 import org.jetbrains.annotations.Contract;
18 import org.jetbrains.annotations.NotNull;
19 import org.jetbrains.annotations.Nullable;
20
21 import javax.swing.*;
22 import java.awt.*;
23 import java.awt.event.ActionEvent;
24 import java.awt.event.ActionListener;
25 import java.util.ArrayList;
26 import java.util.List;
27
28 import static com.intellij.openapi.util.text.HtmlChunk.*;
29
30 public class CustomizeIDEWizardDialog extends DialogWrapper implements CommonCustomizeIDEWizardDialog {
31   protected static final String BUTTONS = "BUTTONS";
32   protected static final String NO_BUTTONS = "NO_BUTTONS";
33
34   protected final JButton mySkipButton = new JButton(IdeBundle.message("button.skip.remaining.and.set.defaults"));
35   protected final JButton myBackButton = new JButton(IdeBundle.message("button.back"));
36   protected final JButton myNextButton = new JButton(IdeBundle.message("button.next"));
37
38   protected final JBCardLayout myCardLayout = new JBCardLayout();
39   protected final List<AbstractCustomizeWizardStep> mySteps = new ArrayList<>();
40   protected int myIndex = 0;
41   protected final JBLabel myNavigationLabel = new JBLabel();
42   protected final JBLabel myHeaderLabel = new JBLabel();
43   protected final JBLabel myFooterLabel = new JBLabel();
44   protected final CardLayout myButtonWrapperLayout = new CardLayout();
45   protected final JPanel myButtonWrapper = new JPanel(myButtonWrapperLayout);
46   protected JPanel myContentPanel;
47   protected final boolean myHideSkipButton;
48
49   public CustomizeIDEWizardDialog(@NotNull CustomizeIDEWizardStepsProvider stepsProvider) {
50     this(stepsProvider, null, true, true);
51   }
52
53   public CustomizeIDEWizardDialog(@NotNull CustomizeIDEWizardStepsProvider stepsProvider, @Nullable StartupUtil.AppStarter appStarter,
54                                   boolean beforeSplash, boolean afterSplash) {
55     super(null, true, true);
56     setTitle(IdeBundle.message("dialog.title.customize.0", ApplicationNamesInfo.getInstance().getFullProductName()));
57     getPeer().setAppIcons();
58
59     if (beforeSplash) stepsProvider.initSteps(this, mySteps);
60     if (afterSplash) stepsProvider.initStepsAfterSplash(this, mySteps);
61
62     if (appStarter != null) {
63       int newIndex = appStarter.customizeIdeWizardDialog(mySteps);
64       if (newIndex != -1) {
65         myIndex = newIndex;
66       }
67     }
68
69     myHideSkipButton = (mySteps.size() <= 1) || stepsProvider.hideSkipButton();
70
71     if (mySteps.isEmpty()) {
72       close(CANCEL_EXIT_CODE);
73       return;
74     }
75
76     mySkipButton.addActionListener(this);
77     myBackButton.addActionListener(this);
78     myNextButton.addActionListener(this);
79     AbstractCustomizeWizardStep.applyHeaderFooterStyle(myNavigationLabel);
80     AbstractCustomizeWizardStep.applyHeaderFooterStyle(myHeaderLabel);
81     AbstractCustomizeWizardStep.applyHeaderFooterStyle(myFooterLabel);
82     init();
83     initCurrentStep(true);
84     setSize(400, 300);
85     System.setProperty(StartupActionScriptManager.STARTUP_WIZARD_MODE, "true");
86   }
87
88   @Override
89   public final void show() {
90     if (mySteps.isEmpty()) {
91       throw new IllegalStateException("no steps provided");  // use showIfNeeded() instead
92     }
93     CustomizeIDEWizardInteractions.INSTANCE.record(CustomizeIDEWizardInteractionType.WizardDisplayed);
94     SplashManager.executeWithHiddenSplash(getWindow(), () -> super.show());
95   }
96
97   @Override
98   public final boolean showIfNeeded() {
99     boolean willBeShown = !mySteps.isEmpty() && !isDisposed();
100     if (willBeShown) {
101       show();
102     }
103     return willBeShown;
104   }
105
106   @Override
107   protected @NotNull DialogStyle getStyle() {
108     return DialogStyle.COMPACT;
109   }
110
111   @Override
112   protected void dispose() {
113     System.clearProperty(StartupActionScriptManager.STARTUP_WIZARD_MODE);
114     super.dispose();
115   }
116
117   @Override
118   protected JComponent createCenterPanel() {
119     JPanel result = new JPanel(new BorderLayout(5, 5));
120     myContentPanel = new JPanel(myCardLayout);
121     for (AbstractCustomizeWizardStep step : mySteps) {
122       myContentPanel.add(step, step.getTitle());
123     }
124     JPanel topPanel = new JPanel(new BorderLayout(5, 5));
125     if (mySteps.size() > 1) {
126       topPanel.add(myNavigationLabel, BorderLayout.NORTH);
127     }
128     topPanel.add(myHeaderLabel, BorderLayout.CENTER);
129     result.add(topPanel, BorderLayout.NORTH);
130     result.add(myContentPanel, BorderLayout.CENTER);
131     result.add(myFooterLabel, BorderLayout.SOUTH);
132     result.setPreferredSize(JBUI.size(700, 600));
133     result.setBorder(AbstractCustomizeWizardStep.createSmallEmptyBorder());
134     return result;
135   }
136
137   @Override
138   protected JComponent createSouthPanel() {
139     final JPanel buttonPanel = new JPanel(new GridBagLayout());
140     GridBagConstraints gbc = new GridBagConstraints();
141     gbc.insets.right = 5;
142     gbc.fill = GridBagConstraints.BOTH;
143     gbc.gridx = 0;
144     gbc.gridy = 0;
145
146     if (!myHideSkipButton)
147       buttonPanel.add(mySkipButton, gbc);
148
149     gbc.gridx++;
150     buttonPanel.add(myBackButton, gbc);
151     gbc.gridx++;
152     gbc.weightx = 1;
153     buttonPanel.add(Box.createHorizontalGlue(), gbc);
154     gbc.gridx++;
155     gbc.weightx = 0;
156     buttonPanel.add(myNextButton, gbc);
157     buttonPanel.setBorder(BorderFactory.createEmptyBorder(8, 0, 0, 0));
158     myButtonWrapper.add(buttonPanel, BUTTONS);
159     myButtonWrapper.add(new JLabel(), NO_BUTTONS);
160     myButtonWrapperLayout.show(myButtonWrapper, BUTTONS);
161     return myButtonWrapper;
162   }
163
164   void setButtonsVisible(boolean visible) {
165     myButtonWrapperLayout.show(myButtonWrapper, visible ? BUTTONS : NO_BUTTONS);
166   }
167
168   @Override
169   public void actionPerformed(@NotNull ActionEvent e) {
170     if (e.getSource() == mySkipButton) {
171       CustomizeIDEWizardInteractions.INSTANCE.setSkippedOnPage(myIndex);
172       doOKAction();
173       return;
174     }
175     if (e.getSource() == myBackButton) {
176       myIndex--;
177       initCurrentStep(false);
178       return;
179     }
180     if (e.getSource() == myNextButton) {
181       if (myIndex >= mySteps.size() - 1) {
182         doOKAction();
183         return;
184       }
185       myIndex++;
186       initCurrentStep(true);
187     }
188   }
189
190   @Nullable
191   @Override
192   protected ActionListener createCancelAction() {
193     return null;//Prevent closing by <Esc>
194   }
195
196   @Override
197   public void doCancelAction() {
198     doOKAction();
199   }
200
201   @Override
202   protected void doOKAction() {
203     for (AbstractCustomizeWizardStep step : mySteps) {
204       if (!step.beforeOkAction()) {
205         int index = mySteps.indexOf(step);
206         if (myIndex != index) {
207           myIndex = index;
208           initCurrentStep(true);
209         }
210         return;
211       }
212     }
213     super.doOKAction();
214   }
215
216   @Override
217   protected boolean canRecordDialogId() {
218     return false;
219   }
220
221   protected void initCurrentStep(boolean forward) {
222     final AbstractCustomizeWizardStep myCurrentStep = mySteps.get(myIndex);
223     myCurrentStep.beforeShown(forward);
224     myCardLayout.swipe(myContentPanel, myCurrentStep.getTitle(), JBCardLayout.SwipeDirection.AUTO, () -> {
225       Component component = myCurrentStep.getDefaultFocusedComponent();
226       if (component != null) {
227         IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(() -> IdeFocusManager.getGlobalInstance().requestFocus(component, true));
228       }
229     });
230
231     myBackButton.setVisible(myIndex > 0);
232     if (myIndex > 0) {
233       myBackButton.setText(IdeBundle.message("button.back.to.0", mySteps.get(myIndex - 1).getTitle()));
234     }
235
236     myNextButton.setText(myIndex < mySteps.size() - 1
237                          ? IdeBundle.message("button.next.0", mySteps.get(myIndex + 1).getTitle())
238                          : IdeBundle.message("button.start.using.0", ApplicationNamesInfo.getInstance().getFullProductName()));
239     myHeaderLabel.setText(ensureHTML(myCurrentStep.getHTMLHeader()));
240     myFooterLabel.setText(ensureHTML(myCurrentStep.getHTMLFooter()));
241     if (mySteps.size() > 1) {
242       HtmlChunk.Element body = HtmlChunk.body();
243       String arrow = myNavigationLabel.getFont().canDisplay(0x2192) ? "&#8594;" : "&gt;";
244       for (int i = 0; i < mySteps.size(); i++) {
245         if (i > 0) {
246           body = body.children(nbsp(), raw(arrow), nbsp());
247         }
248         if (i == myIndex) {
249           body = body.children(
250             tag("b").addText(mySteps.get(i).getTitle()));
251         } else {
252           body = body.addText(mySteps.get(i).getTitle());
253         }
254       }
255       String navHtml = new HtmlBuilder().append(HtmlChunk.html().child(body)).toString();
256
257       myNavigationLabel.setText(navHtml);
258     }
259   }
260
261   @Contract(value = "!null->!null" ,pure = true)
262   private static String ensureHTML(@Nullable String s) {
263     return s == null ? null : s.startsWith("<html>") ? s : "<html>" + StringUtil.escapeXmlEntities(s) + "</html>";
264   }
265 }