com.android.tools.idea.wizard.model.ModelWizardDialog.java Source code

Java tutorial

Introduction

Here is the source code for com.android.tools.idea.wizard.model.ModelWizardDialog.java

Source

/*
 * Copyright (C) 2016 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.model;

import com.android.tools.idea.ui.properties.BindingsManager;
import com.android.tools.idea.ui.properties.InvalidationListener;
import com.android.tools.idea.ui.properties.ListenerManager;
import com.android.tools.idea.ui.properties.ObservableValue;
import com.android.tools.idea.ui.properties.core.BoolValueProperty;
import com.android.tools.idea.ui.properties.core.ObservableBool;
import com.android.tools.idea.ui.properties.core.ObservableOptional;
import com.android.tools.idea.ui.properties.core.ObservableString;
import com.android.tools.idea.ui.properties.expressions.bool.BooleanExpressions;
import com.android.tools.idea.ui.properties.swing.EnabledProperty;
import com.android.tools.idea.ui.properties.swing.VisibleProperty;
import com.intellij.ide.BrowserUtil;
import com.intellij.ide.IdeBundle;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.SystemInfo;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.net.URL;

import static com.android.tools.idea.ui.properties.expressions.bool.BooleanExpressions.not;

/**
 * A dialog container which drives an underlying {@link ModelWizard}, decorating it with UI.
 * <p/>
 * Note that the dialog owns responsibility for starting the wizard. If you start it externally
 * first, this dialog will throw an exception on {@link #show()}.
 */
public final class ModelWizardDialog extends DialogWrapper implements ModelWizard.ResultListener {
    public enum CancellationPolicy {
        ALWAYS_CAN_CANCEL, CAN_CANCEL_UNTIL_CAN_FINISH
    }

    private CancellationPolicy myCancellationPolicy = CancellationPolicy.ALWAYS_CAN_CANCEL;

    @SuppressWarnings("NullableProblems") // Always NotNull but initialized indirectly in constructor
    @NotNull
    private ModelWizard myWizard;

    private BindingsManager myBindings = new BindingsManager();
    private ListenerManager myListeners = new ListenerManager();

    @Nullable
    private CustomLayout myCustomLayout;
    @Nullable
    private URL myHelpUrl;

    @NotNull
    @Override
    protected Action[] createLeftSideActions() {
        return new Action[] { new StepActionWrapper() };
    }

    public ModelWizardDialog(@NotNull ModelWizard wizard, @NotNull String title,
            @Nullable CustomLayout customLayout, @Nullable Project project, @Nullable URL helpUrl,
            @NotNull IdeModalityType modalityType, @NotNull CancellationPolicy cancellationPolicy) {
        super(project, true, modalityType);
        init(wizard, title, customLayout, helpUrl, cancellationPolicy);
    }

    public ModelWizardDialog(@NotNull ModelWizard wizard, @NotNull String title, @NotNull Component parent,
            @Nullable CustomLayout customLayout, @Nullable URL helpUrl,
            @NotNull CancellationPolicy cancellationPolicy) {
        super(parent, true);
        init(wizard, title, customLayout, helpUrl, cancellationPolicy);
    }

    private void init(@NotNull ModelWizard wizard, @NotNull String title, @Nullable CustomLayout customLayout,
            @Nullable URL helpUrl, @NotNull CancellationPolicy cancellationPolicy) {
        Disposer.register(getDisposable(), wizard);
        myWizard = wizard;
        myWizard.addResultListener(this);
        myCustomLayout = customLayout;
        myHelpUrl = helpUrl;
        myCancellationPolicy = cancellationPolicy;
        setTitle(title);

        init();

        if (customLayout != null) {
            Disposer.register(wizard, customLayout);
        }
    }

    @Override
    protected void dispose() {
        super.dispose();
        myBindings.releaseAll();
        myListeners.releaseAll();
        myWizard.removeResultListener(this);
    }

    @Override
    public void show() {
        // TODO: Why is this necessary? Why is DialogWrapper ignoring setSize unless I do this?
        getContentPanel().setMinimumSize(getSize());
        super.show();
    }

    @NotNull
    @Override
    protected DialogStyle getStyle() {
        return DialogStyle.COMPACT; // Remove padding from this dialog, we'll fill it in ourselves
    }

    @Nullable
    @Override
    protected JComponent createCenterPanel() {
        JPanel wizardContent = myWizard.getContentPanel();
        JPanel centerPanel;
        if (myCustomLayout != null) {
            centerPanel = myCustomLayout.decorate(myWizard.title(), wizardContent);
        } else {
            centerPanel = wizardContent;
        }
        return centerPanel;
    }

    @Override
    protected void doHelpAction() {
        // This should never be called unless myHelpUrl is non-null (see createActions)
        assert myHelpUrl != null;
        BrowserUtil.browse(myHelpUrl);
    }

    @Override
    public void doCancelAction() {
        myWizard.cancel();
        // DON'T call super.doCancelAction - that's triggered by onWizardFinished
    }

    @Override
    public void doOKAction() {
        // OK doesn't work directly. This dialog only closes when the underlying wizard closes.
        // super.doOKAction is triggered by onWizardFinished
    }

    @Override
    public void onWizardFinished(boolean success) {
        // Only progress when we know the underlying wizard is done. Call the super methods directly
        // since we stubbed out our local overrides.
        if (success) {
            super.doOKAction();
        } else {
            super.doCancelAction();
        }
    }

    @NotNull
    @Override
    protected Action[] createActions() {
        NextAction nextAction = new NextAction();
        PreviousAction prevAction = new PreviousAction();
        FinishAction finishAction = new FinishAction();
        CancelAction cancelAction = new CancelAction(myCancellationPolicy);
        if (myHelpUrl == null) {
            if (SystemInfo.isMac) {
                return new Action[] { cancelAction, prevAction, nextAction, finishAction };
            }

            return new Action[] { prevAction, nextAction, cancelAction, finishAction };
        } else {
            if (SystemInfo.isMac) {
                return new Action[] { getHelpAction(), cancelAction, prevAction, nextAction, finishAction };
            }
            return new Action[] { prevAction, nextAction, cancelAction, finishAction, getHelpAction() };
        }
    }

    @Nullable
    @Override
    public JComponent getPreferredFocusedComponent() {
        return myWizard.getPreferredFocusComponent();
    }

    @Override
    protected JButton createJButtonForAction(Action action) {
        final JButton button = super.createJButtonForAction(action);

        if (action instanceof ModelWizardDialogAction) {
            ModelWizardDialogAction wizardAction = (ModelWizardDialogAction) action;
            myBindings.bind(new EnabledProperty(button), wizardAction.shouldBeEnabled());
            myBindings.bind(new VisibleProperty(button), wizardAction.shouldBeVisible());
            myListeners.receiveAndFire(wizardAction.shouldBeDefault(), isDefault -> {
                JRootPane rootPane = getRootPane();
                if (rootPane != null && isDefault) {
                    rootPane.setDefaultButton(button);
                }
            });
        }

        return button;
    }

    /**
     * A layout provider which, if set, gives the wizard dialog a custom look and feel.
     * <p/>
     * By default, a wizard dialog simply displays the contents of a wizard, undecorated. However,
     * a custom look and feel lets you inject a custom theme and titlebar into your wizard.
     */
    public interface CustomLayout extends Disposable {
        @NotNull
        JPanel decorate(@NotNull ObservableString title, @NotNull JPanel innerPanel);
    }

    /**
     * The model wizard exposes various boolean properties representing its current navigable state.
     * By associating actions with those properties, we can easily bind UI buttons to them.
     */
    private abstract class ModelWizardDialogAction extends DialogWrapperAction {
        public ModelWizardDialogAction(@NotNull String name) {
            super(name);
        }

        @NotNull
        public abstract ObservableBool shouldBeEnabled();

        @NotNull
        public ObservableBool shouldBeVisible() {
            return BooleanExpressions.alwaysTrue();
        }

        @NotNull
        public ObservableBool shouldBeDefault() {
            return BooleanExpressions.alwaysFalse();
        }
    }

    private final class NextAction extends ModelWizardDialogAction {
        NextAction() {
            super(IdeBundle.message("button.wizard.next"));
        }

        @Override
        protected void doAction(ActionEvent e) {
            myWizard.goForward();
        }

        @Override
        @NotNull
        public ObservableBool shouldBeEnabled() {
            return myWizard.canGoForward().and(not(myWizard.onLastStep()));
        }

        @NotNull
        @Override
        public ObservableBool shouldBeDefault() {
            return not(myWizard.onLastStep());
        }
    }

    private final class PreviousAction extends ModelWizardDialogAction {
        PreviousAction() {
            super(IdeBundle.message("button.wizard.previous"));
        }

        @Override
        protected void doAction(ActionEvent e) {
            myWizard.goBack();
        }

        @Override
        @NotNull
        public ObservableBool shouldBeEnabled() {
            return myWizard.canGoBack();
        }
    }

    private final class FinishAction extends ModelWizardDialogAction {
        FinishAction() {
            super(IdeBundle.message("button.finish"));
        }

        @Override
        protected void doAction(ActionEvent e) {
            myWizard.goForward();
        }

        @Override
        @NotNull
        public ObservableBool shouldBeEnabled() {
            return myWizard.onLastStep().and(myWizard.canGoForward());
        }

        @NotNull
        @Override
        public ObservableBool shouldBeDefault() {
            return myWizard.onLastStep();
        }
    }

    private final class CancelAction extends ModelWizardDialogAction {
        private final CancellationPolicy myCancellationPolicy;

        private CancelAction(@NotNull CancellationPolicy cancellationPolicy) {
            super(IdeBundle.message("button.cancel"));
            myCancellationPolicy = cancellationPolicy;
        }

        @Override
        protected void doAction(ActionEvent e) {
            doCancelAction();
        }

        @Override
        @NotNull
        public ObservableBool shouldBeEnabled() {
            switch (myCancellationPolicy) {
            case CAN_CANCEL_UNTIL_CAN_FINISH:
                return not(myWizard.onLastStep().and(myWizard.canGoForward()));
            case ALWAYS_CAN_CANCEL:
            default:
                return BooleanExpressions.alwaysTrue();
            }
        }
    }

    /**
     * A {@link ModelWizardDialogAction} that behaves (in terms of name, enabled status, and actual action implementation) like the
     * {@link ModelWizardStep#getExtraAction() extra action} of the current step in our wizard. If the current step has no extra action,
     * {@link #shouldBeVisible()} will be false.
     */
    private final class StepActionWrapper extends ModelWizardDialogAction {
        private final BoolValueProperty myEnabled = new BoolValueProperty(false);
        private final ObservableOptional<Action> myExtraAction;
        private PropertyChangeListener myActionListener;

        @NotNull
        @Override
        public ObservableBool shouldBeVisible() {
            return myExtraAction.isPresent();
        }

        public StepActionWrapper() {
            super("");

            myExtraAction = myWizard.getExtraAction();

            InvalidationListener extraActionChangedListener = new InvalidationListener() {
                Action myActiveAction = null;

                @Override
                public void onInvalidated(@NotNull ObservableValue<?> sender) {
                    if (myActiveAction != null && myActionListener != null) {
                        myActiveAction.removePropertyChangeListener(myActionListener);
                    }
                    myActiveAction = myExtraAction.getValueOrNull();
                    if (myActiveAction != null) {
                        StepActionWrapper.this.putValue(NAME, myActiveAction.getValue(NAME));
                        myActionListener = new PropertyChangeListener() {
                            @Override
                            public void propertyChange(PropertyChangeEvent evt) {
                                if ("enabled".equals(evt.getPropertyName())) {
                                    myEnabled.set((Boolean) evt.getNewValue());
                                }
                            }
                        };
                        myActiveAction.addPropertyChangeListener(myActionListener);
                        myEnabled.set(myActiveAction.isEnabled());
                    } else {
                        myActionListener = null;
                    }
                }
            };
            myExtraAction.addListener(extraActionChangedListener);
            extraActionChangedListener.onInvalidated(myExtraAction);
        }

        @NotNull
        @Override
        public ObservableBool shouldBeEnabled() {
            return myEnabled;
        }

        @Override
        protected void doAction(ActionEvent e) {
            assert myExtraAction.get().isPresent();
            myExtraAction.getValue().actionPerformed(e);
        }
    }
}