Package org.openquark.gems.client

Source Code of org.openquark.gems.client.GemCodePanel$PanelState

/*
* Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
*     * Redistributions of source code must retain the above copyright notice,
*       this list of conditions and the following disclaimer.
*     * Redistributions in binary form must reproduce the above copyright
*       notice, this list of conditions and the following disclaimer in the
*       documentation and/or other materials provided with the distribution.
*     * Neither the name of Business Objects nor the names of its contributors
*       may be used to endorse or promote products derived from this software
*       without specific prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/


/*
* GemCodePanel.java
* Creation date: (1/18/01 1:17:15 PM)
* By: Luke Evans
*/
package org.openquark.gems.client;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Hashtable;
import java.util.List;
import java.util.TooManyListenersException;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.ScrollPaneConstants;
import javax.swing.TransferHandler;
import javax.swing.text.BadLocationException;
import javax.swing.text.Highlighter;
import javax.swing.text.Highlighter.Highlight;

import org.openquark.cal.compiler.CodeAnalyser;
import org.openquark.cal.compiler.ModuleTypeInfo;
import org.openquark.cal.services.CALWorkspace;
import org.openquark.cal.services.Perspective;
import org.openquark.gems.client.EditorScrollPane.ErrorOffset;
import org.openquark.gems.client.caleditor.AdvancedCALEditor;
import org.openquark.gems.client.caleditor.AdvancedCALEditor.AmbiguityOffset;
import org.openquark.gems.client.utilities.MouseClickDragAdapter;


/**
* The GemCodePanel is a set of components, most importantly containing an instance of the
* CALEditor.  It is designed to allow the definition of a new Gem through the use of CAL
* code.  The user types only the RHS of a supercombinator definition into the editor, and the
* CodeGemEditor which contains this panel does the rest.
* Creation date: (1/18/01 1:17:15 PM)
* @author Luke Evans
*/
class GemCodePanel extends JPanel {
    private static final long serialVersionUID = -47798849290936051L;

    /** Whether the output type is considered good.  We keep track of this only to persist undo state. */
    private boolean outputGood = true;

    /** The displayed icon when the output is locked. */
    private static final ImageIcon lockedIcon;

    /** The display area for variables in the code panel. */
    private VariablesDisplay variablesDisplay;
   
    /** The display are for qualifications in the code panel. */
    private QualificationsDisplay qualificationsDisplay;

    /** The CAL editor into which code is entered. */
    private AdvancedCALEditor ivjCALEditorPane = null;
   
    /** The bottom panel that contains the message labels. */
    private JPanel ivjBottomPanel = null;
   
    /** The panel that displays the output type. */
    private JLabel ivjOutputTypeLabel = null;
   
    /** The scroll pane that contains the editor text area. */
    private EditorScrollPane ivjEditorScrollPane = null;
   
    /** The label for the error message. */
    private JLabel ivjMessageLabel = null;
   
    /** The split pane that contains the editor pane and the panel displays. */
    private JSplitPane ivjJSplitPane1 = null;
   
    /** The split pane that contains the qualifications & variables displays. */
    private JSplitPane displaysSplitPane = null;
   
    /** The perspective this panel was initialized with. */
    private final Perspective perspective;
   
    static {
        lockedIcon = new ImageIcon(GemCodePanel.class.getResource("/Resources/smallkey.gif"));
    }

    /**
     * A class that can hold the different elements needed for saving and restoring the state.
     * @author Edward Lam
     */
    private static class PanelState {

        final String errorMessage;
        final String outputTypeText;
        final String outputToolTipText;
        final boolean outputGood;
        final boolean outputLocked;

        private PanelState(GemCodePanel gemCodePanel) {

            String errorMessage = gemCodePanel.getMessageLabel().getText();
            if (errorMessage != null && errorMessage.length() == 0) {
                errorMessage = null;
            }
            this.errorMessage = errorMessage;

            JLabel typeLabel = gemCodePanel.getOutputTypeLabel();
            String outputTypeText = typeLabel.getText();
            if (outputTypeText != null && outputTypeText.length() == 0) {
                outputTypeText = null;
            }
            this.outputTypeText = outputTypeText;

            String outputToolTipText = typeLabel.getToolTipText();
            if (outputToolTipText != null && outputToolTipText.length() == 0) {
                outputToolTipText = null;
            }
            this.outputToolTipText = outputToolTipText;

            this.outputGood = gemCodePanel.outputGood;
            outputLocked = (typeLabel.getIcon() != null);
        }

        @Override
        public boolean equals(Object obj) {

            if (!(obj instanceof PanelState)) {
                return false;
            }

            PanelState ps = (PanelState)obj;

            return (errorMessage != null ? errorMessage.equals(ps.errorMessage) : true) &&
                    (outputTypeText != null ? outputTypeText.equals(ps.outputTypeText) : true) &&
                    (outputToolTipText != null ? outputToolTipText.equals(ps.outputToolTipText) : true) &&
                    outputGood == ps.outputGood &&
                    outputLocked == ps.outputLocked;
        }
       
        @Override
        public int hashCode() {
            return 325 * (257 + (errorMessage != null ? errorMessage.hashCode() : 0)) +
                   197 * (145 + (outputTypeText != null ? outputTypeText.hashCode() : 0)) +
                   101 * (65 + (outputToolTipText != null ? outputToolTipText.hashCode() : 0)) +
                    37 * (17 + (outputGood? Boolean.TRUE : Boolean.FALSE).hashCode()) +
                    (outputLocked ? Boolean.TRUE : Boolean.FALSE).hashCode();
        }
    }
   
    /**
     * A specialized CALEditor that displays a tooltip if the mouse hovers over source errors
     * @author Frank Worsley
     */
    private class GemCodeEditor extends AdvancedCALEditor {
        private static final long serialVersionUID = -8391489120285354572L;

        public GemCodeEditor(ModuleTypeInfo moduleTypeInfo, CALWorkspace workspace) {
            super(moduleTypeInfo, workspace);
            setToolTipText("GemCodeEditor");
        }
       
        @Override
        public String getToolTipText(MouseEvent e) {

            try {
               
                int textOffset = getUI().viewToModel(this, e.getPoint());
               
                // See how closely the returned text offset actually matches the cursor position.
                // If the mouse hovers anywhere to the right of the last character on a line, then
                // the returned offset will be the offset of that character. This is because that
                // character is closest to the mouse position.
                Rectangle offsetRect = getUI().modelToView(this, textOffset);
               
                // The offset rectangle is very small, we need to make it a
                // little bigger so that it is not to difficult to get a tooltip.
                offsetRect.x -= 5;
                offsetRect.y -= 5;
                offsetRect.width += 10;
                offsetRect.height += 10;
               
                if (!offsetRect.contains(e.getPoint())) {
                    // If the mouse is just hovering on empty space to the right
                    // of a line, then don't show a tooltip.
                    return null;
                }
                   
                // Build tooltip if there is an error at this position

                List<ErrorOffset> errorOffsets = ivjEditorScrollPane.getErrorOffsets();
                for (final ErrorOffset offset : errorOffsets) {
               
                    if (textOffset >= offset.getStartOffset() && textOffset <= offset.getEndOffset()) {
                        return "<html><body>" + ivjEditorScrollPane.getMessageToolTip(offset.getCompilerMessage()) + "</body></html>";
                    }
                }
               
                // There is no error at this position; so show metadata tooltip via superclass
                return super.getToolTipText(e);
                   
            } catch (BadLocationException ex) {
                throw new IllegalStateException("bad location displaying tooltip");
            }       
        }
    }

    /**
     * Default GemCodePanel constructor.
     * @param codeGem the related code gem.
     * @param gemCodeSyntaxListener a gem code syntax listener for the code panel
     * @param perspective the perspective to use to resolve entity names for tooltips
     */
    public GemCodePanel(CodeGem codeGem, GemCodeSyntaxListener gemCodeSyntaxListener, Perspective perspective) {

        this.perspective = perspective;

        // set the code we're editing
        String source = codeGem.getVisibleCode();
        getCALEditorPane().setText(source);
       
        setLayout(new BorderLayout());
        add(getBottomPanel(), "South");
        add(getJSplitPane1(), "Center");
       
        // Push focus onto the editor when we receive focus.
        addFocusListener(new FocusAdapter() {
            @Override
            public void focusGained(FocusEvent e) {
                getCALEditorPane().requestFocus();   
            }
        });

        // Add a style listener
        try {
            getCALEditorPane().addCALSyntaxStyleListener(gemCodeSyntaxListener);
        } catch (TooManyListenersException e) {
            throw new IllegalStateException();
        }   
    }

    /**
     * Return the source code.
     * Creation date: (1/26/01 4:46:19 PM)
     * @return the source code
     */
    public String getCode() {
        return getCALEditorPane().getText();
    }

    /**
     * Adds an indicator for the given compiler message. An indicator will put a little
     * 'x' into the side panel next to the line the error occurs in and will highlight
     * the source that caused the error.
     * @param message
     */
    void addErrorIndicator(CodeAnalyser.OffsetCompilerMessage message) {
        getEditorScrollPane().addErrorIndicator(message);
        setErrorMessage(GemCutter.getResourceString("CGE_Broken"));
    }
   
    /**
     * Removes all error indicators.
     */
    void clearErrorIndicators() {
        getEditorScrollPane().clearErrorIndicators();
        setErrorMessage(null);
    }
   
    /**
     * Refreshes the ambiguity indicators on the editor panel
     */
    public void updateAmbiguityIndicators() {
        ivjCALEditorPane.updateAmbiguityIndicators();
    }
   
    /**
     * Displays the given error message.
     * @param message the error message to display (use null to clear)
     */
    void setErrorMessage(String message) {
        getMessageLabel().setText(message);
        getMessageLabel().setToolTipText(ToolTipHelpers.wrapTextToHTMLLines(message, this));
    }
   
    /**
     * Set the list of analyzed identifiers from the code
     * @param sourceIdentifiers (List of AnalysedIdentifier)
     */
    void setSourceIdentifiers(List<CodeAnalyser.AnalysedIdentifier> sourceIdentifiers) {
        ivjCALEditorPane.setSourceIdentifiers(sourceIdentifiers);
    }
   
    /*
     * Methods to access GUI components *******************************************************************
     */

    /**
     * Return the BottomPanel property value.
     * @return JPanel
     */
    private JPanel getBottomPanel() {
        if (ivjBottomPanel == null) {
            try {
                ivjBottomPanel = new JPanel();
                ivjBottomPanel.setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1));
                ivjBottomPanel.setLayout(new BoxLayout(ivjBottomPanel, BoxLayout.X_AXIS));
                ivjBottomPanel.add(getOutputTypeLabel());
                ivjBottomPanel.add(Box.createHorizontalGlue());
                ivjBottomPanel.add(getMessageLabel());
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return ivjBottomPanel;
    }

    /**
     * Return the CALEditorPane property value.
     * @return org.openquark.gems.client.CALEditor
     */
    AdvancedCALEditor getCALEditorPane() {
        if (ivjCALEditorPane == null) {
            try {
                ivjCALEditorPane = new GemCodeEditor(perspective.getWorkingModuleTypeInfo(), perspective.getWorkspace());
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return ivjCALEditorPane;
    }

    /**
     * @return EditorScrollPane the scrollpane for the CALEditor.
     */
    private EditorScrollPane getEditorScrollPane() {
        if (ivjEditorScrollPane == null) {
            try {
                ivjEditorScrollPane = new EditorScrollPane(getCALEditorPane());
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return ivjEditorScrollPane;
    }

    /**
     * Get the variables display associated with this panel.
     * Creation date: (Jul 16, 2002 4:36:52 PM)
     * @return VariablesDisplay the associated VariablesDisplay.
     */
    VariablesDisplay getVariablesDisplay() {
        if (variablesDisplay == null) {
            variablesDisplay = new VariablesDisplay();
            variablesDisplay.addListFocusListener(new FocusAdapter() {
                 @Override
                public void focusLost(FocusEvent e) {
                    // deselect item if focus lost
                    variablesDisplay.clearSelection();
                }
            });
        }
        return variablesDisplay;
    }
   
    /**
     * Get the qualifications display associated with this panel.
     * @return VariablesDisplay the associated VariablesDisplay.
     */
    QualificationsDisplay getQualificationsDisplay() {
        if (qualificationsDisplay == null) {
            qualificationsDisplay = new QualificationsDisplay(perspective.getWorkspace());
            qualificationsDisplay.addListFocusListener(new FocusAdapter() {
                 @Override
                public void focusLost(FocusEvent e) {
                    // deselect item if focus lost
                    qualificationsDisplay.clearSelection();
                }
            });
        }
        return qualificationsDisplay;
    }
   
    /**
     * Return the variables JSplitPane1 .
     * @return JSplitPane
     */
    private JSplitPane getDisplaysSplitPane() {
        if (displaysSplitPane == null) {
            try {
                displaysSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
                displaysSplitPane.setName("JSplitPane1");
                displaysSplitPane.setOneTouchExpandable(true);
                displaysSplitPane.setTopComponent(getVariablesDisplay());
                displaysSplitPane.setBottomComponent(getQualificationsDisplay());
                displaysSplitPane.setDividerLocation(0.5);

            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return displaysSplitPane;
    }

    /**
     * Return the main JSplitPane1 property value.
     * @return JSplitPane
     */
    private JSplitPane getJSplitPane1() {
        if (ivjJSplitPane1 == null) {
            try {
                ivjJSplitPane1 = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
                ivjJSplitPane1.setName("JSplitPane1");
                ivjJSplitPane1.setDividerSize(2);
                getJSplitPane1().add(getEditorScrollPane(), "right");
                getJSplitPane1().add(getDisplaysSplitPane(), "left");

            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return ivjJSplitPane1;
    }

    /**
     * Return the Message property value.
     * @return JLabel
     */
    private JLabel getMessageLabel() {
        if (ivjMessageLabel == null) {
            try {
                ivjMessageLabel = new JLabel();
                ivjMessageLabel.setName("Message");
                ivjMessageLabel.setText("");
                ivjMessageLabel.setForeground(Color.red);

            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return ivjMessageLabel;
    }

    /**
     * Return the OutputTypeLabel property value.
     * @return JLabel
     */
    private JLabel getOutputTypeLabel() {
        if (ivjOutputTypeLabel == null) {
            try {
                ivjOutputTypeLabel = new JLabel();
                ivjOutputTypeLabel.setName("OutputType");
                ivjOutputTypeLabel.setText("-> " + GemCutter.getResourceString("CGE_Undefined_Type"));

            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return ivjOutputTypeLabel;
    }

    /**
     * Update the label that displays the output type of the code gem.
     * @param typeText the text of the label.  If non-null, can optionally be wrapped in \<html\> tags
     * @param toolTipText the text to display for the tooltip.
     * @param locked whether the output is locked
     * @param good whether the output is considered good
     */
    void updateOutputTypeLabel(String typeText, String toolTipText, boolean locked, boolean good) {

        JLabel typeLabel = getOutputTypeLabel();

        typeLabel.setIcon(locked ? lockedIcon : null);
        typeLabel.setText(typeText);
        if (toolTipText == null) {
            typeLabel.setToolTipText(typeText);
        } else {
            typeLabel.setToolTipText(toolTipText);
        }

        outputGood = good;
        if (good) {
            typeLabel.setBackground(Color.black);
            typeLabel.setForeground(Color.black);
        } else {
            typeLabel.setBackground(Color.red);
            typeLabel.setForeground(Color.red);
        }
    }

    /**
     * Called whenever the part throws an exception.
     * @param exception Throwable
     */
    private void handleException(Throwable exception) {
   
        /* Uncomment the following lines to print uncaught exceptions to stdout */
         System.out.println("--------- UNCAUGHT EXCEPTION ---------");
         exception.printStackTrace(System.out);
    }

    /*
     * Methods supporting javax.swing.undo.StateEditable ********************************************
     */

    /**
     * Restore the stored state.
     * This will make the panel consistent with the code gem and displayed code gem, so update those first!
     * Creation date: (04/02/2002 6:12:00 PM)
     * @param state Hashtable the stored state
     */
    public void restoreState(Hashtable<?, ?> state) {

        PanelState panelState = (PanelState)state.get(this);
        if (panelState != null) {
            setErrorMessage(panelState.errorMessage);

            updateOutputTypeLabel(panelState.outputTypeText, panelState.outputToolTipText,
                    panelState.outputLocked, panelState.outputGood);
        }
    }

    /**
     * Save the state.
     * Creation date: (04/02/2002 6:12:00 PM)
     * @param state Hashtable the table in which to store the state
     */
    public void storeState(Hashtable<Object, Object> state) {

        state.put(this, new PanelState(this));
    }
}

/**
* A special scrollpane to use for the CALEditor component. It manages the side panel
* that displays little error icons for lines with errors in them.
* @author Frank Worsley
*/
class EditorScrollPane extends JScrollPane {

    private static final long serialVersionUID = -3660183287664833150L;

    /**
     * This class implements a little side panel that displays error icons
     * next to the line of code that has an error in it.
     * @author Frank Worsley
     */
    private class SidePanel extends JPanel {
   
        private static final long serialVersionUID = -5172117175135917290L;

        /**
         * Mouse handler to receive clicks on error icons.
         */
        private class MouseHandler extends MouseClickDragAdapter {
            /**
             * Constructor for the Mouse Handler
             */
            private MouseHandler() {
            }
           
            /**
             * Surrogate method for mouseClicked.  Called only when our definition of click occurs.
             *
             * Clicking on an ambiguity indicator causes the editor to select the first
             * ambiguity on the line, and display a menu for that identifier.
             *
             * @param e MouseEvent the relevant event
             * @return boolean true if the click was a double click
             */
            @Override
            public boolean mouseReallyClicked(MouseEvent e){

                AdvancedCALEditor editor = EditorScrollPane.this.getEditor();
               
                boolean doubleClicked = super.mouseReallyClicked(e);
               
                for (final AmbiguityOffset offset : getEditor().getAmbiguityOffsets()) {

                    int y = offset.getLineNumber() * lineHeight + lineOffset;
                    Rectangle iconRect = new Rectangle(2, y+2, ambiguityIcon.getIconWidth()+2, ambiguityIcon.getIconHeight()+2);
                   
                    if (iconRect.contains(e.getPoint())) {
                        // Select ambiguity
                        editor.select(offset.getStartOffset(), offset.getEndOffset());
                       
                        // Display menu at ambiguity location
                       
                        AdvancedCALEditor.IdentifierPopupMenuProvider popupProvider = editor.getPopupMenuProvider();
                        if (popupProvider == null) {
                            break;
                        }
                       
                        Point menuPoint;
                        try {
                            Rectangle offsetRect = editor.getUI().modelToView(editor, offset.getStartOffset());
                            menuPoint = new Point(offsetRect.x, offsetRect.y + offsetRect.height);
                        } catch (BadLocationException ex) {
                            throw new IllegalStateException("bad location displaying ambiguity popup");
                        }
                       
                        CodeAnalyser.AnalysedIdentifier identifier = offset.getIdentifier();
                        AdvancedCALEditor.PositionlessIdentifier positionlessIdentifier =
                            new AdvancedCALEditor.PositionlessIdentifier(identifier.getName(), identifier.getRawModuleName(), identifier.getResolvedModuleName(), identifier.getMinimallyQualifiedModuleName(), identifier.getCategory(), identifier.getQualificationType());
                       
                        JPopupMenu menu = popupProvider.getPopupMenu(positionlessIdentifier);
                        menu.show(getEditor(), menuPoint.x, menuPoint.y);
                       
                        break;
                       
                    }
                }
               
                return doubleClicked;
            }
        }
       
        /** The height in pixels of a single line of text. */
        private final int lineHeight;
   
        /** The offset from the top to draw the error icon at. */
        private final int lineOffset;
       
        /**
         * Constructor for a new SidePanel.
         */
        public SidePanel() {

            FontMetrics metrics = getEditor().getFontMetrics(getEditor().getFont());
            Insets margin = getEditor().getMargin();
       
            this.lineHeight = metrics.getHeight();
            this.lineOffset = margin != null ? margin.top : 0;
       
            setOpaque(true);
            setToolTipText("SidePanel");
            setBackground(Color.LIGHT_GRAY);
            setPreferredSize(new Dimension(errorIcon.getIconWidth(), getEditor().getHeight()));
            addMouseListener(new MouseHandler());
           
            // We need to grow to the same size if the editor resizes
            getEditor().addComponentListener(new ComponentAdapter() {
                @Override
                public void componentResized(ComponentEvent e) {
                    setPreferredSize(new Dimension(errorIcon.getIconWidth(), getEditor().getHeight()));
                }
            });
        }
   
        /**
         * Draws the error and ambiguity for the side panel.
         * If multiple errors or ambiguities occur on the same line, the icons
         * will indicate multiplicity.
         * If ambiguity icons occur on a line, the line will contain typecheck
         * errors, but only the ambiguity icons will be displayed.
         *
         * @param g the graphics object to draw with
         */
        @Override
        public void paintComponent(Graphics g) {
       
            super.paintComponent(g);
           
            // Show ambiguity indicators
            List<Integer> ambiguityLines = new ArrayList<Integer>();
            for (final AmbiguityOffset offset : getEditor().getAmbiguityOffsets()) {
               
                Integer lineNum = Integer.valueOf(offset.getLineNumber());
               
                int y = offset.getLineNumber() * lineHeight + lineOffset;
                if (!ambiguityLines.contains(lineNum)) {
                    g.drawImage(ambiguityIcon.getImage(), 2, y+2, this);
                    ambiguityLines.add(lineNum);
                } else {
                    g.drawImage(ambiguityIcon.getImage(), 4, y+4, this);
                }
            }
           
            // Show error indicators
            List<Integer> errorLines = new ArrayList<Integer>();
            for (final ErrorOffset offset : errorOffsets) {
               
                Integer lineNum = Integer.valueOf(offset.getLineNumber());

                if (!ambiguityLines.contains(lineNum)) {
                    // Ambiguity does not have
                    int y = offset.getLineNumber() * lineHeight + lineOffset;
                    if (!errorLines.contains(lineNum)) {
                        g.drawImage(errorIcon.getImage(), -1, y, this);
                        errorLines.add(lineNum);
                    } else {
                        g.drawImage(errorIcon.getImage(), 1, y+1, this);
                    }
                }
            }
        }
   
        /**
         * @param e the MouseEvent that triggered the tooltip
         * @return the error description within a tooltip if the mouse is over an error icon.
         */
        @Override
        public String getToolTipText(MouseEvent e) {
            return getToolTipText(e.getPoint());
        }
       
        /**
         * @param mousePoint the location of the mouse
         * @return the error description within a tooltip if the point is over an error icon.
         */
        private String getToolTipText(Point mousePoint) {

            List<String> messages = new ArrayList<String>();

            for (final ErrorOffset offset : errorOffsets) {

                int y = offset.getLineNumber() * lineHeight + lineOffset;
                Rectangle iconRect = new Rectangle(0, y, errorIcon.getIconWidth(), errorIcon.getIconHeight());
       
                if (iconRect.contains(mousePoint)) {
                    messages.add(getMessageToolTip(offset.getCompilerMessage()));
                }
            }

            if (messages.isEmpty()) {
                return null;
               
            } else if (messages.size() == 1) {
                return "<html><body>" + messages.get(0) + "</body></html>";
               
            } else {
                StringBuilder buffer = new StringBuilder();
               
                buffer.append("<html><body>");
                buffer.append("<b>" + GemCutter.getResourceString("CGE_MultipleErrors") + "</b>");
               
                for (final String message : messages) {
                    buffer.append("<br><br>");
                    buffer.append(message);
                }

                buffer.append("</html></body>");               
                return buffer.toString();
            }
        }
    }

    /**
     * A convenience class for storing a compiler message and the associated error position.
     * @author Frank Worsley
     */
    static class ErrorOffset {
       
        private final CodeAnalyser.OffsetCompilerMessage message;
        private final AdvancedCALEditor.EditorLocation errorLocation;
       
        private ErrorOffset(CodeAnalyser.OffsetCompilerMessage message, AdvancedCALEditor.EditorLocation offset) {
           
            if (message == null) {
                throw new NullPointerException();
            }
           
            this.message = message;
            this.errorLocation = offset;
        }
       
        public int getStartOffset() {
            return errorLocation.getStartOffset();
        }
       
        public int getEndOffset() {
            return errorLocation.getEndOffset();
        }
       
        public int getLineNumber() {
            return errorLocation.getLineNumber();
        }
       
        public CodeAnalyser.OffsetCompilerMessage getCompilerMessage() {
            return message;
        }
    }
   

    /** The icon to draw to indicate there is an error on a line. */
    private static final ImageIcon errorIcon = new ImageIcon(GemCodePanel.class.getResource("/Resources/error.gif"));
   
    /** The icon to draw to indicate there is an ambiguity on a line. */
    private static final ImageIcon ambiguityIcon = new ImageIcon(GemCodePanel.class.getResource("/Resources/smallquery.gif"));

    /** The side panel that displays the error icons. */
    private final SidePanel sidePanel = new SidePanel();
   
    /** The set of error offsets that must be indicated. */
    private final List<ErrorOffset> errorOffsets = new ArrayList<ErrorOffset>();
       
    /**
     * Constructs a new scrollpane for the given editor.
     * @param editor the editor
     */
    public EditorScrollPane(AdvancedCALEditor editor) {
        super (editor);

        setRowHeaderView(sidePanel);
        setTransferHandler(new TransferHandler("text"));       
        setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
        setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
    }

    /**
     * @return the editor this scrollpane is for
     */
    public AdvancedCALEditor getEditor() {
        return (AdvancedCALEditor) getViewport().getView();
    }
   
    /**
     * Adds an error indicate for the given compiler message.
     * @param message the message to display an indicator for
     */
    public void addErrorIndicator(CodeAnalyser.OffsetCompilerMessage message) {
       
        ErrorOffset offset = getErrorOffset(message);
       
        if (offset == null) {
            return;
        }
       
        errorOffsets.add(offset);
        sidePanel.repaint();
               
        try {
            Highlighter highlighter = getEditor().getHighlighter();
            highlighter.addHighlight(offset.getStartOffset(), offset.getEndOffset(), new ErrorUnderlineHighlightPainter());
           
        } catch (BadLocationException ex) {
            throw new IllegalStateException("bad location adding highlight");
        }
    }
   
    /**
     * Clears all messages and removes all error indicators.
     */
    public void clearErrorIndicators() {
       
        // We can't simply remove all highlighters, since if the user has text selected,
        // there will be a highlighter to indicate the text selection. This fixes a bug
        // where selected text becomes invisible after the syntax smarts run, because
        // the selection highlight was removed here.
        Highlighter highlighter = getEditor().getHighlighter();
        Highlight[] highlights = highlighter.getHighlights();
       
        for (final Highlight highlight : highlights) {
            if (highlight.getPainter() instanceof ErrorUnderlineHighlightPainter) {
                highlighter.removeHighlight(highlight);
            }
        }

        errorOffsets.clear();
        sidePanel.repaint();
    }
   
    /**
     * @return the ErrorOffsets for the error indicators
     */
    public List<ErrorOffset> getErrorOffsets() {
        return Collections.unmodifiableList(errorOffsets);
    }

    /**
     * @param message the message to build a tooltip for
     * @return an HTML formatted tooltip for the given message
     */   
    public String getMessageToolTip(CodeAnalyser.OffsetCompilerMessage message) {
           
        StringBuilder buffer = new StringBuilder();

        buffer.append("<b>");
        buffer.append(ToolTipHelpers.wrapTextToHTMLLines(message.getMessage().replaceAll("<", "&lt;").replaceAll(">", "&gt;"), this));

        buffer.append("</b>");
       
        Exception ex = message.getException();
        if (ex != null) {

            // Add caused by message.
            buffer.append("<br>");
            buffer.append(ToolTipHelpers.wrapTextToHTMLLines(ex.getMessage().replaceAll("<", "&lt;").replaceAll(">", "&gt;"), this));
        }

        return buffer.toString();
    }

    /**
     * Converts the source position of the error into a text offset, length, and line number.
     * @param message the compiler message to convert the source position for.
     */
    private ErrorOffset getErrorOffset(CodeAnalyser.OffsetCompilerMessage message) {

        if (!message.hasPosition()) {
            return null;
        }

        try {

            AdvancedCALEditor.EditorLocation offset = getEditor().getEditorTokenOffset(message.getOffsetLine(), message.getOffsetColumn(), -1);
            return new ErrorOffset(message, offset);
           
        } catch (BadLocationException ex) {
            throw new IllegalArgumentException("invalid location trying to convert compiler message source position");
        }
    }

}

/** Highlight painter for errors */
class ErrorUnderlineHighlightPainter extends AdvancedCALEditor.UnderlineHighlightPainter {

    private static final Color LINE_COLOR = Color.RED;
    @Override
    public Color getLineColor() {
        return LINE_COLOR;
    }
}
TOP

Related Classes of org.openquark.gems.client.GemCodePanel$PanelState

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.