blob: dfc7af85165ddee12b2ec52d0adfa469f082bf28 [file] [log] [blame]
/*
* 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.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.WizardConstants.STUDIO_WIZARD_INSETS;
import static com.android.tools.idea.wizard.WizardConstants.STUDIO_WIZARD_INSET_SIZE;
import static com.android.tools.idea.wizard.dynamic.ScopedStateStore.createKey;
/**
* <p>Base class for wizard steps that have a description label in the bottom.
* One of the facilities provided by this class is tracking currently focused
* component and displaying its description.</p>
* <p>Subclasses should call {@link #setBodyComponent(javax.swing.JComponent)}
* from the constructor.</p>
*/
public abstract class DynamicWizardStepWithDescription extends DynamicWizardStep implements Disposable {
protected static final ScopedStateStore.Key<String> KEY_DESCRIPTION =
createKey(DynamicWizardStepWithDescription.class + ".description", ScopedStateStore.Scope.STEP, String.class);
private static final String PROPERTY_FOCUS_OWNER = "focusOwner";
@Nullable private final Disposable myDisposable;
private PropertyChangeListener myFocusListener;
private JPanel myRootPane;
private JLabel myDescriptionLabel;
private JBLabel myErrorWarningLabel;
private JPanel mySouthPanel;
private Map<Component, String> myControlDescriptions = new WeakHashMap<Component, String>();
public DynamicWizardStepWithDescription(@Nullable Disposable parentDisposable) {
myDisposable = parentDisposable;
if (parentDisposable != null) {
Disposer.register(parentDisposable, this);
}
mySouthPanel.setBorder(new EmptyBorder(STUDIO_WIZARD_INSETS));
myErrorWarningLabel.setForeground(JBColor.red);
// Set to empty HTML. If completely empty the height calculation is off and window resizing results.
myErrorWarningLabel.setText("<html></html>");
myDescriptionLabel.setText("<html></html>");
}
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);
}
@NotNull
@Override
protected JPanel createStepBody() {
return myRootPane;
}
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 != null && !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(new Insets(STUDIO_WIZARD_INSET_SIZE, STUDIO_WIZARD_INSET_SIZE, STUDIO_WIZARD_INSET_SIZE, STUDIO_WIZARD_INSET_SIZE)));
myRootPane.add(component, BorderLayout.CENTER);
}
@Override
public void init() {
register(KEY_DESCRIPTION, getDescriptionLabel(), new ComponentBinding<String, JLabel>() {
@Override
public void setValue(String newValue, @NotNull JLabel label) {
label.setText(toHtml(newValue));
}
});
}
/**
* Subclasses may override this method if they want to provide a custom description label.
*/
protected JLabel getDescriptionLabel() {
return myDescriptionLabel;
}
@NotNull
@Override
public final JBLabel getMessageLabel() {
return myErrorWarningLabel;
}
@Nullable
protected Disposable getDisposable() {
return myDisposable;
}
}