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;
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;
23 import java.awt.event.ActionEvent;
24 import java.awt.event.ActionListener;
25 import java.util.ArrayList;
26 import java.util.List;
28 import static com.intellij.openapi.util.text.HtmlChunk.*;
30 public class CustomizeIDEWizardDialog extends DialogWrapper implements CommonCustomizeIDEWizardDialog {
31 protected static final String BUTTONS = "BUTTONS";
32 protected static final String NO_BUTTONS = "NO_BUTTONS";
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"));
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;
49 public CustomizeIDEWizardDialog(@NotNull CustomizeIDEWizardStepsProvider stepsProvider) {
50 this(stepsProvider, null, true, true);
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();
59 if (beforeSplash) stepsProvider.initSteps(this, mySteps);
60 if (afterSplash) stepsProvider.initStepsAfterSplash(this, mySteps);
62 if (appStarter != null) {
63 int newIndex = appStarter.customizeIdeWizardDialog(mySteps);
69 myHideSkipButton = (mySteps.size() <= 1) || stepsProvider.hideSkipButton();
71 if (mySteps.isEmpty()) {
72 close(CANCEL_EXIT_CODE);
76 mySkipButton.addActionListener(this);
77 myBackButton.addActionListener(this);
78 myNextButton.addActionListener(this);
79 AbstractCustomizeWizardStep.applyHeaderFooterStyle(myNavigationLabel);
80 AbstractCustomizeWizardStep.applyHeaderFooterStyle(myHeaderLabel);
81 AbstractCustomizeWizardStep.applyHeaderFooterStyle(myFooterLabel);
83 initCurrentStep(true);
85 System.setProperty(StartupActionScriptManager.STARTUP_WIZARD_MODE, "true");
89 public final void show() {
90 if (mySteps.isEmpty()) {
91 throw new IllegalStateException("no steps provided"); // use showIfNeeded() instead
93 CustomizeIDEWizardInteractions.INSTANCE.record(CustomizeIDEWizardInteractionType.WizardDisplayed);
94 SplashManager.executeWithHiddenSplash(getWindow(), () -> super.show());
98 public final boolean showIfNeeded() {
99 boolean willBeShown = !mySteps.isEmpty() && !isDisposed();
107 protected void dispose() {
108 System.clearProperty(StartupActionScriptManager.STARTUP_WIZARD_MODE);
113 protected JComponent createCenterPanel() {
114 JPanel result = new JPanel(new BorderLayout(5, 5));
115 myContentPanel = new JPanel(myCardLayout);
116 for (AbstractCustomizeWizardStep step : mySteps) {
117 myContentPanel.add(step, step.getTitle());
119 JPanel topPanel = new JPanel(new BorderLayout(5, 5));
120 if (mySteps.size() > 1) {
121 topPanel.add(myNavigationLabel, BorderLayout.NORTH);
123 topPanel.add(myHeaderLabel, BorderLayout.CENTER);
124 result.add(topPanel, BorderLayout.NORTH);
125 result.add(myContentPanel, BorderLayout.CENTER);
126 result.add(myFooterLabel, BorderLayout.SOUTH);
127 result.setPreferredSize(JBUI.size(700, 600));
128 result.setBorder(AbstractCustomizeWizardStep.createSmallEmptyBorder());
133 protected JComponent createSouthPanel() {
134 final JPanel buttonPanel = new JPanel(new GridBagLayout());
135 GridBagConstraints gbc = new GridBagConstraints();
136 gbc.insets.right = 5;
137 gbc.fill = GridBagConstraints.BOTH;
141 if (!myHideSkipButton)
142 buttonPanel.add(mySkipButton, gbc);
145 buttonPanel.add(myBackButton, gbc);
148 buttonPanel.add(Box.createHorizontalGlue(), gbc);
151 buttonPanel.add(myNextButton, gbc);
152 buttonPanel.setBorder(BorderFactory.createEmptyBorder(8, 0, 0, 0));
153 myButtonWrapper.add(buttonPanel, BUTTONS);
154 myButtonWrapper.add(new JLabel(), NO_BUTTONS);
155 myButtonWrapperLayout.show(myButtonWrapper, BUTTONS);
156 return myButtonWrapper;
159 void setButtonsVisible(boolean visible) {
160 myButtonWrapperLayout.show(myButtonWrapper, visible ? BUTTONS : NO_BUTTONS);
164 public void actionPerformed(@NotNull ActionEvent e) {
165 if (e.getSource() == mySkipButton) {
166 CustomizeIDEWizardInteractions.INSTANCE.setSkippedOnPage(myIndex);
170 if (e.getSource() == myBackButton) {
172 initCurrentStep(false);
175 if (e.getSource() == myNextButton) {
176 if (myIndex >= mySteps.size() - 1) {
181 initCurrentStep(true);
187 protected ActionListener createCancelAction() {
188 return null;//Prevent closing by <Esc>
192 public void doCancelAction() {
197 protected void doOKAction() {
198 for (AbstractCustomizeWizardStep step : mySteps) {
199 if (!step.beforeOkAction()) {
200 int index = mySteps.indexOf(step);
201 if (myIndex != index) {
203 initCurrentStep(true);
212 protected boolean canRecordDialogId() {
216 protected void initCurrentStep(boolean forward) {
217 final AbstractCustomizeWizardStep myCurrentStep = mySteps.get(myIndex);
218 myCurrentStep.beforeShown(forward);
219 myCardLayout.swipe(myContentPanel, myCurrentStep.getTitle(), JBCardLayout.SwipeDirection.AUTO, () -> {
220 Component component = myCurrentStep.getDefaultFocusedComponent();
221 if (component != null) {
222 IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(() -> IdeFocusManager.getGlobalInstance().requestFocus(component, true));
226 myBackButton.setVisible(myIndex > 0);
228 myBackButton.setText(IdeBundle.message("button.back.to.0", mySteps.get(myIndex - 1).getTitle()));
231 myNextButton.setText(myIndex < mySteps.size() - 1
232 ? IdeBundle.message("button.next.0", mySteps.get(myIndex + 1).getTitle())
233 : IdeBundle.message("button.start.using.0", ApplicationNamesInfo.getInstance().getFullProductName()));
234 myHeaderLabel.setText(ensureHTML(myCurrentStep.getHTMLHeader()));
235 myFooterLabel.setText(ensureHTML(myCurrentStep.getHTMLFooter()));
236 if (mySteps.size() > 1) {
237 HtmlChunk.Element body = HtmlChunk.body();
238 String arrow = myNavigationLabel.getFont().canDisplay(0x2192) ? "→" : ">";
239 for (int i = 0; i < mySteps.size(); i++) {
241 body = body.children(nbsp(), raw(arrow), nbsp());
244 body = body.children(
245 tag("b").addText(mySteps.get(i).getTitle()));
247 body = body.addText(mySteps.get(i).getTitle());
250 String navHtml = new HtmlBuilder().append(HtmlChunk.html().child(body)).toString();
252 myNavigationLabel.setText(navHtml);
256 @Contract(value = "!null->!null" ,pure = true)
257 private static String ensureHTML(@Nullable String s) {
258 return s == null ? null : s.startsWith("<html>") ? s : "<html>" + StringUtil.escapeXmlEntities(s) + "</html>";