org.cordovastudio.editors.designer.inspection.AbstractQuickFixManager.java Source code

Java tutorial

Introduction

Here is the source code for org.cordovastudio.editors.designer.inspection.AbstractQuickFixManager.java

Source

/*
 * Copyright 2000-2012 JetBrains s.r.o.
 *
 * 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 org.cordovastudio.editors.designer.inspection;

import com.intellij.codeHighlighting.HighlightDisplayLevel;
import com.intellij.designer.model.ErrorInfo;
import com.intellij.designer.model.QuickFix;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.keymap.KeymapUtil;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.ui.popup.ListPopup;
import com.intellij.openapi.ui.popup.PopupStep;
import com.intellij.openapi.ui.popup.util.BaseListPopupStep;
import com.intellij.openapi.wm.impl.VisibilityWatcher;
import com.intellij.ui.ClickListener;
import com.intellij.ui.HintHint;
import com.intellij.ui.LightweightHint;
import com.intellij.ui.RowIcon;
import com.intellij.util.Alarm;
import com.intellij.util.IJSwingUtilities;
import com.intellij.util.ThrowableRunnable;
import com.intellij.util.ui.EmptyIcon;
import org.cordovastudio.editors.designer.CordovaDesignerBundle;
import org.cordovastudio.editors.designer.designSurface.CordovaDesignerEditorPanel;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.*;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.List;

/**
 * @author Alexander Lobas
 * @author Anton Katilin
 * @author Vladimir Kondratyev
 */
public abstract class AbstractQuickFixManager {
    protected CordovaDesignerEditorPanel myDesigner;
    protected final JComponent myComponent;
    private final JViewport myViewPort;
    private final Alarm myAlarm = new Alarm();
    private final Runnable myShowHintRequest;
    private LightweightHint myHint;
    private Rectangle myLastHintBounds;

    public AbstractQuickFixManager(@Nullable CordovaDesignerEditorPanel designer, JComponent component,
            JViewport viewPort) {
        myDesigner = designer;
        myComponent = component;
        myViewPort = viewPort;

        myShowHintRequest = new Runnable() {
            @Override
            public void run() {
                showHint();
            }
        };

        new VisibilityWatcher() {
            @Override
            public void visibilityChanged() {
                if (myComponent.isShowing()) {
                    updateHintVisibility();
                } else {
                    hideHint();
                }
            }
        }.install(component);

        component.addFocusListener(new FocusListener() {
            @Override
            public void focusGained(FocusEvent e) {
                if (!e.isTemporary()) {
                    updateHintVisibility();
                }
            }

            @Override
            public void focusLost(FocusEvent e) {
                if (!(e.isTemporary())) {
                    hideHint();
                }
            }
        });

        AnAction showHintAction = new AnAction() {
            @Override
            public void actionPerformed(AnActionEvent e) {
                if (myDesigner != null) {
                    showHint();
                    showPopup();
                }
            }

            @Override
            public void update(AnActionEvent e) {
                e.getPresentation().setEnabled(e.getData(CommonDataKeys.EDITOR) == null);
            }
        };
        showHintAction.registerCustomShortcutSet(
                ActionManager.getInstance().getAction(IdeActions.ACTION_SHOW_INTENTION_ACTIONS).getShortcutSet(),
                component);

        viewPort.addChangeListener(new ChangeListener() {
            @Override
            public void stateChanged(ChangeEvent e) {
                updateHintPosition();
            }
        });
    }

    public void setDesigner(@Nullable CordovaDesignerEditorPanel designer) {
        myDesigner = designer;
    }

    public void update() {
        if (!myComponent.isShowing() || !IJSwingUtilities.hasFocus(myComponent)) {
            hideHint();
        } else if (myHint == null || !myHint.isVisible()) {
            updateHintVisibility();
        } else {
            Rectangle bounds = getErrorBounds();
            if (!ErrorInfo.haveFixes(getErrorInfos()) || bounds == null || !bounds.equals(myLastHintBounds)) {
                hideHint();
                updateHintVisibility();
            }
        }
    }

    private void showHint() {
        if (!myComponent.isShowing() || !IJSwingUtilities.hasFocus(myComponent)) {
            hideHint();
            return;
        }

        // 1. Hide previous hint (if any)
        hideHint();

        // 2. Found error (if any)
        List<ErrorInfo> infos = getErrorInfos();
        if (!ErrorInfo.haveFixes(infos)) {
            hideHint();
            return;
        }

        boolean error = false;
        for (ErrorInfo errorInfo : infos) {
            if (errorInfo.getLevel() == HighlightDisplayLevel.ERROR) {
                error = true;
                break;
            }
        }

        // 3. Determine position where this hint should be shown
        Rectangle bounds = getErrorBounds();
        if (bounds == null) {
            return;
        }

        // 4. Show light bulb to fix this error
        myHint = new LightweightHint(
                new InspectionHint(error ? AllIcons.Actions.QuickfixBulb : AllIcons.Actions.IntentionBulb));
        myLastHintBounds = bounds;
        myHint.show(myComponent, bounds.x - AllIcons.Actions.IntentionBulb.getIconWidth() - 4, bounds.y,
                myComponent, new HintHint(myComponent, bounds.getLocation()));
    }

    private void showPopup() {
        if (myHint == null || !myHint.isVisible()) {
            return;
        }

        List<ErrorInfo> errorInfos = getErrorInfos();
        if (!ErrorInfo.haveFixes(errorInfos)) {
            return;
        }

        ListPopup popup = JBPopupFactory.getInstance().createListPopup(new FirstStep(errorInfos));
        popup.showUnderneathOf(myHint.getComponent());
    }

    public final void hideHint() {
        myAlarm.cancelAllRequests();
        if (myHint != null && myHint.isVisible()) {
            myHint.hide();
            myHint = null;
            myComponent.paintImmediately(myComponent.getVisibleRect());
        }
    }

    protected final void updateHintVisibility() {
        myAlarm.cancelAllRequests();
        myAlarm.addRequest(myShowHintRequest, 500);
    }

    private void updateHintPosition() {
        if (myHint != null && myHint.isVisible()) {
            Rectangle rc = getErrorBounds();
            if (rc != null) {
                myLastHintBounds = rc;
                Rectangle hintRect = new Rectangle(rc.x - AllIcons.Actions.IntentionBulb.getIconWidth() - 4, rc.y,
                        AllIcons.Actions.IntentionBulb.getIconWidth() + 4,
                        AllIcons.Actions.IntentionBulb.getIconHeight() + 4);
                if (getHintClipRect().contains(hintRect)) {
                    myHint.updateLocation(hintRect.x, hintRect.y);
                } else {
                    myHint.hide();
                }
            }
        }
    }

    protected Rectangle getHintClipRect() {
        return myViewPort.getViewRect();
    }

    /**
     * @return error info for the current {@link #myComponent} state.
     */
    @NotNull
    protected abstract List<ErrorInfo> getErrorInfos();

    /**
     * @return rectangle (in {@link #myComponent} coordinates) that represents
     *         area that contains errors. This methods is invoked only if {@link #getErrorInfos()}
     *         returned non empty list of error infos. <code>null</code> means that
     *         error bounds are not defined.
     */
    @Nullable
    protected abstract Rectangle getErrorBounds();

    //////////////////////////////////////////////////////////////////////////////////////////
    //
    //
    //
    //////////////////////////////////////////////////////////////////////////////////////////

    private class FirstStep extends BaseListPopupStep<ErrorInfo> {
        public FirstStep(List<ErrorInfo> errorInfos) {
            super(null, errorInfos);
        }

        @Override
        public Icon getIconFor(ErrorInfo value) {
            return AllIcons.Actions.RealIntentionBulb;
        }

        @NotNull
        @Override
        public String getTextFor(ErrorInfo value) {
            return value.getName();
        }

        @Override
        public PopupStep onChosen(ErrorInfo value, boolean finalChoice) {
            List<QuickFix> quickFixes = value.getQuickFixes();
            if (finalChoice) {
                return doFinalStep(getQuickFixRunnable(quickFixes.get(0)));
            }
            return new SecondStep(quickFixes);
        }

        @Override
        public boolean hasSubstep(ErrorInfo selectedValue) {
            return true;
        }

        @Override
        public boolean isAutoSelectionEnabled() {
            return false;
        }
    }

    private class SecondStep extends BaseListPopupStep<QuickFix> {
        public SecondStep(List<QuickFix> fixList) {
            super(null, fixList);
        }

        @Override
        public Icon getIconFor(QuickFix value) {
            return value.getIcon();
        }

        @NotNull
        @Override
        public String getTextFor(QuickFix value) {
            return value.getName();
        }

        @Override
        public PopupStep onChosen(QuickFix value, boolean finalChoice) {
            return doFinalStep(getQuickFixRunnable(value));
        }
    }

    private Runnable getQuickFixRunnable(final QuickFix value) {
        return new Runnable() {
            @Override
            public void run() {
                myDesigner.getToolProvider().executeWithReparse(new ThrowableRunnable<Exception>() {
                    @Override
                    public void run() throws Exception {
                        ApplicationManager.getApplication().runWriteAction(value);
                    }
                }, "Run '" + value.getName() + "' QuickFix");
            }
        };
    }

    private static final Border INACTIVE_BORDER = BorderFactory.createEmptyBorder(4, 4, 4, 4);
    private static final Border ACTIVE_BORDER = BorderFactory.createCompoundBorder(
            BorderFactory.createLineBorder(Color.orange, 2), BorderFactory.createEmptyBorder(2, 2, 2, 2));

    private static final Icon INACTIVE_ARROW_ICON = new EmptyIcon(AllIcons.General.ArrowDown.getIconWidth(),
            AllIcons.General.ArrowDown.getIconHeight());

    private class InspectionHint extends JLabel {
        private final RowIcon myInactiveIcon;
        private final RowIcon myActiveIcon;

        private InspectionHint(Icon icon) {
            setOpaque(false);
            setBorder(INACTIVE_BORDER);

            myActiveIcon = new RowIcon(2);
            myActiveIcon.setIcon(icon, 0);
            myActiveIcon.setIcon(AllIcons.General.ArrowDown, 1);

            myInactiveIcon = new RowIcon(2);
            myInactiveIcon.setIcon(icon, 0);
            myInactiveIcon.setIcon(INACTIVE_ARROW_ICON, 1);

            setIcon(myInactiveIcon);

            String acceleratorsText = KeymapUtil.getFirstKeyboardShortcutText(
                    ActionManager.getInstance().getAction(IdeActions.ACTION_SHOW_INTENTION_ACTIONS));
            if (acceleratorsText.length() > 0) {
                setToolTipText(CordovaDesignerBundle.message("tooltip.press.accelerator", acceleratorsText));
            }

            addMouseListener(new MouseAdapter() {
                @Override
                public void mouseEntered(MouseEvent e) {
                    setIcon(myActiveIcon);
                    setBorder(ACTIVE_BORDER);
                }

                @Override
                public void mouseExited(MouseEvent e) {
                    setIcon(myInactiveIcon);
                    setBorder(INACTIVE_BORDER);
                }
            });

            new ClickListener() {
                @Override
                public boolean onClick(@NotNull MouseEvent event, int clickCount) {
                    showPopup();
                    return true;
                }
            }.installOn(this);
        }
    }
}