/* * 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; import com.intellij.openapi.Disposable; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.text.StringUtil; import com.intellij.ui.JBColor; import com.intellij.ui.components.JBLabel; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.border.Border; import javax.swing.border.CompoundBorder; import javax.swing.border.EmptyBorder; import java.awt.*; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.Map; import java.util.WeakHashMap; import static com.android.tools.idea.wizard.ScopedStateStore.Key; import static com.android.tools.idea.wizard.ScopedStateStore.createKey; /** * Base class for wizard steps with standard design. * * Subclasses should call {@link #setBodyComponent(javax.swing.JComponent)} from the constructor. */ public abstract class DynamicWizardStepWithHeaderAndDescription extends DynamicWizardStep implements Disposable { protected static final Key<String> KEY_DESCRIPTION = createKey(DynamicWizardStepWithHeaderAndDescription.class + ".description", ScopedStateStore.Scope.STEP, String.class); protected static final Key<String> KEY_TITLE = createKey(DynamicWizardStepWithHeaderAndDescription.class + ".title", ScopedStateStore.Scope.STEP, String.class); protected static final Key<String> KEY_MESSAGE = createKey(DynamicWizardStepWithHeaderAndDescription.class + ".message", ScopedStateStore.Scope.STEP, String.class); private static final String PROPERTY_FOCUS_OWNER = "focusOwner"; @NotNull private final String myTitle; @Nullable private final String myMessage; @Nullable private final Disposable myDisposable; private PropertyChangeListener myFocusListener; private JPanel myRootPane; private JBLabel myTitleLabel; private JBLabel myMessageLabel; private JBLabel myIcon; private JLabel myDescriptionText; private JBLabel myErrorWarningLabel; private JPanel myNorthPanel; private JPanel myCustomHeaderPanel; private JPanel myTitlePanel; private JPanel mySouthPanel; private Map<Component, String> myControlDescriptions = new WeakHashMap<Component, String>(); /** * @deprecated Use {@link #DynamicWizardStepWithHeaderAndDescription(String, String, javax.swing.Icon, com.intellij.openapi.Disposable)} * to properly deregister focus listener when this page is no longer needed */ @Deprecated public DynamicWizardStepWithHeaderAndDescription(@NotNull String title, @Nullable String message, @Nullable Icon icon) { this(title, message, icon, Disposer.newDisposable()); } public DynamicWizardStepWithHeaderAndDescription(@NotNull String title, @Nullable String message, @Nullable Icon icon, @Nullable Disposable parentDisposable) { myDisposable = parentDisposable; if (parentDisposable != null) { Disposer.register(parentDisposable, this); } myTitle = title; myMessage = message; myIcon.setIcon(icon); int fontHeight = myMessageLabel.getFont().getSize(); myTitleLabel.setBorder(BorderFactory.createEmptyBorder(fontHeight, 0, fontHeight, 0)); mySouthPanel.setBorder(new EmptyBorder(WizardConstants.STUDIO_WIZARD_INSETS)); if (getTitleBackgroundColor() != null) { myTitlePanel.setBackground(getTitleBackgroundColor()); myNorthPanel.setBackground(getTitleBackgroundColor()); } if (getTitleTextColor() != null) { myTitleLabel.setForeground(getTitleTextColor()); myMessageLabel.setForeground(getTitleTextColor()); } JComponent header = getHeader(); if (header != null) { myCustomHeaderPanel.add(header, BorderLayout.CENTER); myCustomHeaderPanel.setVisible(true); myCustomHeaderPanel.repaint(); myTitlePanel.setBorder(new EmptyBorder(WizardConstants.STUDIO_WIZARD_INSETS)); } else { Insets topSegmentInsets = new Insets(WizardConstants.STUDIO_WIZARD_TOP_INSET, WizardConstants.STUDIO_WIZARD_INSETS.left, WizardConstants.STUDIO_WIZARD_INSETS.bottom, WizardConstants.STUDIO_WIZARD_INSETS.right); myNorthPanel.setBorder(new EmptyBorder(topSegmentInsets)); } Font font = myTitleLabel.getFont(); if (font == null) { font = UIUtil.getLabelFont(); } font = new Font(font.getName(), font.getStyle() | Font.BOLD, font.getSize() + 4); myTitleLabel.setFont(font); myErrorWarningLabel.setForeground(JBColor.red); } protected static CompoundBorder createBodyBorder() { int fontSize = UIUtil.getLabelFont().getSize(); Border insetBorder = BorderFactory.createEmptyBorder(fontSize * 4, fontSize * 2, fontSize * 4, fontSize * 2); return BorderFactory.createCompoundBorder(BorderFactory.createLineBorder(UIUtil.getBorderColor()), insetBorder); } protected void setControlDescription(Component control, @Nullable String description) { if (myFocusListener == null) { myFocusListener = new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { if (evt.getNewValue() instanceof Component) { updateDescription((Component)evt.getNewValue()); } } }; KeyboardFocusManager.getCurrentKeyboardFocusManager().addPropertyChangeListener(PROPERTY_FOCUS_OWNER, myFocusListener); } if (StringUtil.isEmpty(description)) { myControlDescriptions.remove(control); } else { myControlDescriptions.put(control, description); } } private String getDescriptionText(Component component) { while (component instanceof Component && !myControlDescriptions.containsKey(component)) { component = component.getParent(); } return component != null ? myControlDescriptions.get(component) : ""; } private void updateDescription(Component focusedComponent) { myState.put(KEY_DESCRIPTION, getDescriptionText(focusedComponent)); } @Override public void dispose() { if (myFocusListener != null) { KeyboardFocusManager.getCurrentKeyboardFocusManager().removePropertyChangeListener(PROPERTY_FOCUS_OWNER, myFocusListener); } } protected final void setBodyComponent(JComponent component) { component.setBorder(new EmptyBorder(WizardConstants.STUDIO_WIZARD_INSETS)); myRootPane.add(component, BorderLayout.CENTER); } @NotNull protected String getTitle() { return myTitle; } @Override public void init() { myState.put(KEY_TITLE, myTitle); myState.put(KEY_MESSAGE, myMessage); register(KEY_DESCRIPTION, getDescriptionText(), new ComponentBinding<String, JLabel>() { @Override public void setValue(String newValue, @NotNull JLabel component) { setDescriptionText(newValue); } }); register(KEY_TITLE, myTitleLabel, new ComponentBinding<String, JBLabel>() { @Override public void setValue(@Nullable String newValue, @NotNull JBLabel component) { component.setText(newValue); } }); register(KEY_MESSAGE, myMessageLabel, new ComponentBinding<String, JLabel>() { @Override public void setValue(@Nullable String newValue, @NotNull JLabel component) { component.setVisible(!StringUtil.isEmpty(newValue)); component.setText(ImportUIUtil.makeHtmlString(newValue)); } }); } /** * Subclasses may override this method if they want to provide a custom description label. */ protected JLabel getDescriptionText() { return myDescriptionText; } protected final void setDescriptionText(@Nullable String templateDescription) { getDescriptionText().setText(ImportUIUtil.makeHtmlString(templateDescription)); } @Nullable protected JBColor getTitleBackgroundColor() { return null; } @Nullable protected JBColor getTitleTextColor() { return null; } @Nullable protected JComponent getHeader() { return null; } @NotNull @Override public final JComponent getComponent() { return myRootPane; } @NotNull @Override public final JBLabel getMessageLabel() { return myErrorWarningLabel; } @Nullable protected Disposable getDisposable() { return myDisposable; } }