Package com.agifans.picedit.gui

Source Code of com.agifans.picedit.gui.PictureCodeList$PictureCodeListMouseListener

package com.agifans.picedit.gui;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.LinkedList;
import java.util.List;

import javax.swing.AbstractListModel;
import javax.swing.JList;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.ListCellRenderer;
import javax.swing.ListSelectionModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import com.agifans.picedit.picture.Picture;
import com.agifans.picedit.picture.PictureCodeType;
import com.agifans.picedit.picture.PictureChangeListener;
import com.agifans.picedit.picture.PictureCode;
import com.agifans.picedit.types.BrushType;
import com.agifans.picedit.utils.EgaPalette;

/**
* The JList that holds the human readable list of picture codes for the currently
* selected Picture.
*
* @author Lance Ewing
*/
@SuppressWarnings("serial")
public class PictureCodeList extends JList implements PictureChangeListener, ChangeListener, ListSelectionListener {

    /**
     * The Picture whose picture codes will be displayed in this JList.
     */
    private Picture picture;
   
    /**
     * Provides direct access to the model.
     */
    private PictureCodeListModel pictureCodeListModel;
   
    /**
     * Is true if the picture codes are being added to or removed from; otherwise false.
     */
    private boolean pictureCodesAreAdjusting;
   
    /**
     * The popup menu to display when someone right clicks on a PictureCodeList item.
     */
    private PictureCodeListPopupMenu popupMenu;
   
    /**
     * Constructor for PictureCodeList.
     *
     * @param picture The Picture whose picture codes will be displayed in this JList.
     */
    public PictureCodeList(Picture picture) {
        this.picture = picture;
        this.pictureCodeListModel = new PictureCodeListModel();
        this.setModel(pictureCodeListModel);
        this.setFont(new Font("Courier New", Font.BOLD, 10));
        this.setForeground(Color.BLACK);
        this.setFocusable(false);
        this.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
        this.addListSelectionListener(this);
       
        FontMetrics metrics = this.getFontMetrics(this.getFont());
        this.setCellRenderer(new TextCellRenderer(metrics, 130));
       
        // These two settings make the JList SIGNIFICANTLY faster when adding new picture
        // codes within the middle of the list.
        this.setPrototypeCellValue("Start");
        this.setFixedCellWidth(130);
       
        // Set up the popup menu.
        popupMenu = new PictureCodeListPopupMenu();
        this.addMouseListener(new PictureCodeListMouseListener());
    }
   
    /**
     * Popup menu that appears over the picture code list when the right mouse button is clicked.
     */
    class PictureCodeListPopupMenu extends JPopupMenu {
       
        private JMenuItem deleteMenuItem;
       
        /**
         * Constructor for PictureCodeListPopupMenu.
         */
        PictureCodeListPopupMenu() {
            deleteMenuItem = new JMenuItem("Delete");
            deleteMenuItem.addActionListener(new PictureCodeListPopupMenuActionListener());
            add(deleteMenuItem);
        }
       
        /**
         * Refreshes the state of the menu items.
         */
        public void refreshState() {
            boolean deleteEnabledStatus = !picture.getCurrentPictureCode().getType().equals(PictureCodeType.COLOR_DATA);
            // TODO: Add more checks for delete enabled status, e.g. cannot delete if it would create a corrupt picture.
            deleteMenuItem.setEnabled(deleteEnabledStatus);
        }
    }
   
    /**
     * ListModel for the PictureCodeList JList component that holds the human readable picture
     * codes for the currently selected Picture.
     */
    class PictureCodeListModel extends AbstractListModel implements PictureChangeListener {

        /**
         * Gets a human readable form for the picture code item at the given index.
         *
         * @param index The index at which to retrieve the PictureCode.
         *
         * @return The human readable form of the requested PictureCode.
         */
        public Object getElementAt(int index) {
            if (index == 0) {
                return "Start";
            }
           
            LinkedList<PictureCode> pictureCodes = picture.getPictureCodes();
            PictureCode pictureCode = pictureCodes.get(index - 1);
            PictureCode previousPictureCode = null;
           
            String displayText = null;
            if (pictureCode.isActionCode()) {
                PictureCodeType actionCodeType = pictureCode.getType();
                StringBuilder displayTextBuf = new StringBuilder("  ");
                displayTextBuf.append(actionCodeType.getDisplayableText());
                displayText = displayTextBuf.toString();
            } else {
                StringBuilder displayTextBuf = null;
                int code = pictureCode.getCode();
                switch (pictureCode.getType()) {
                    case FILL_POINT_DATA:
                        displayTextBuf = new StringBuilder("    Fill ");
                        displayTextBuf.append((code & 0xFF00) >> 8);
                        displayTextBuf.append(" ");
                        displayTextBuf.append(code & 0x00FF);
                        displayText = displayTextBuf.toString();
                        break;
                    case BRUSH_POINT_DATA:
                        displayTextBuf = new StringBuilder("    Plot ");
                        displayTextBuf.append((code & 0xFF00) >> 8);
                        displayTextBuf.append(" ");
                        displayTextBuf.append(code & 0x00FF);
                        displayText = displayTextBuf.toString();
                        break;
                    case ABSOLUTE_POINT_DATA:
                        previousPictureCode = pictureCodes.get(index  - 2);
                        if (previousPictureCode.isActionCode()) {
                            displayTextBuf = new StringBuilder("    MoveTo ");
                        } else {
                            displayTextBuf = new StringBuilder("    LineTo ");
                        }
                        displayTextBuf.append((code & 0xFF00) >> 8);
                        displayTextBuf.append(" ");
                        displayTextBuf.append(code & 0x00FF);
                        displayText = displayTextBuf.toString();
                        break;
                    case RELATIVE_POINT_DATA:
                        int dx = ((code & 0xF0) >> 4) & 0x0F;
                        int dy = (code & 0x0F);
                        if ((dx & 0x08) > 0) {
                            dx = (-1) * (dx & 0x07);
                        }
                        if ((dy & 0x08) > 0) {
                            dy = (-1) * (dy & 0x07);
                        }
                        StringBuilder displayTextBuilder = new StringBuilder();
                        displayTextBuilder.append("    LineTo ");
                        if (dx >= 0) {
                          displayTextBuilder.append("+");
                        }
                        displayTextBuilder.append(dx);
                        displayTextBuilder.append(" ");
                        if (dy >= 0) {
                          displayTextBuilder.append("+");
                        }
                        displayTextBuilder.append(dy);
                        displayText = displayTextBuilder.toString();
                        break;
                    case X_POSITION_DATA:
                        displayTextBuf = new StringBuilder("    LineTo ");
                        displayTextBuf.append(code);
                        displayTextBuf.append(" +0");
                        displayText = displayTextBuf.toString();
                        break;
                    case Y_POSITION_DATA:
                        displayTextBuf = new StringBuilder("    LineTo +0 ");
                        displayTextBuf.append(code);
                        displayText = displayTextBuf.toString();
                        break;
                    case BRUSH_PATTERN_DATA:
                        displayTextBuf = new StringBuilder("    SetPattern ");
                        displayTextBuf.append(code);
                        displayText = displayTextBuf.toString();
                        break;
                    case BRUSH_TYPE_DATA:
                        displayTextBuf = new StringBuilder("    ");
                        displayTextBuf.append(BrushType.getBrushTypeForBrushCode(pictureCode.getCode()).getDisplayName());
                        displayText = displayTextBuf.toString();
                        break;
                    case COLOR_DATA:
                        displayTextBuf = new StringBuilder("    ");
                        displayTextBuf.append(EgaPalette.COLOR_NAMES[pictureCode.getCode()]);
                        displayText = displayTextBuf.toString();
                        break;
                    case END:
                        displayText = "End";
                        break;
                }
            }
           
            return displayText;
        }

        /**
         * Gets the number of items in the picture code list.
         *
         * @return The number of items in the picture code list.
         */
        public int getSize() {
            int listSize = picture.getPictureCodes().size() + 1;
            return listSize;
        }
       
        /**
         * Completely refreshes the JList content by firing an event to say that every item has
         * changed. This will cause the value of every item to be re-read.
         */
        public void refreshList() {
            fireContentsChanged(this, 0, getSize());
        }

        /**
         * Fires an interval added event to update the JList content to include the newly added items.
         *
         * @param fromIndex The index from which the new items were added.
         * @param toIndex The index to which the new items were added.
         */
        public void pictureCodesAdded(int fromIndex, int toIndex) {
            fireIntervalAdded(this, fromIndex, toIndex);
        }

        /**
         * Fires an interval removed event to update the JList content to exclude the removed items.
         *
         * @param fromIndex The index from which the items were removed.
         * @param toIndex The index to which the items were removed.
         */
        public void pictureCodesRemoved(int fromIndex, int toIndex) {
            fireIntervalRemoved(this, fromIndex, toIndex);
        }

        /**
         * The List Model is not interested in this event.
         */
        public void selectionIntervalCollapsed() {
        }
    }

    /**
     * Completely refreshes the JList content.
     */
    public void refreshList() {
        pictureCodeListModel.refreshList();
    }
   
    /**
     * Invoked when picture codes are added to the Picture. Delegates to the PictureCodeListModel.
     */
    public void pictureCodesAdded(int fromIndex, int toIndex) {
        pictureCodesAreAdjusting = true;
        pictureCodeListModel.pictureCodesAdded(fromIndex, toIndex);
        pictureCodesAreAdjusting = false;
    }

    /**
     * Invoked when picture codes are removed from the Picture. Delegates to the PictureCodeListModel.
     */
    public void pictureCodesRemoved(int fromIndex, int toIndex) {
        pictureCodesAreAdjusting = true;
        pictureCodeListModel.pictureCodesRemoved(fromIndex, toIndex);
        pictureCodesAreAdjusting = false;
    }

    /**
     * Invoked when the picture has forced a collapse of the selection interval.
     */
    public void selectionIntervalCollapsed() {
        setSelectedIndex(getMaxSelectionIndex());
    }

    /**
     * Invoked when the position slider value changes. Keeps the picture code list selected index in sync.
     */
    public void stateChanged(ChangeEvent e) {
        int pictureIndex = picture.getPicturePosition() + 1;
        if (pictureIndex != getMaxSelectionIndex()) {
            setSelectedIndex(pictureIndex);
        }
    }

    /**
     * Invoked when the user selects something on the picture code JList.
     */
    public void valueChanged(ListSelectionEvent e) {
        if (!e.getValueIsAdjusting() && !pictureCodesAreAdjusting) {
            int selectedIndex = this.getMinSelectionIndex();
            if (selectedIndex > 0) {
                int selectedPicturePosition = selectedIndex - 1;
               
                // If an action code is selected in isolation then auto-select the associated data codes.
                List<PictureCode> pictureCodes = picture.getPictureCodes();
                PictureCode pictureCode = pictureCodes.get(selectedPicturePosition);
                if (pictureCode.isActionCode() && (getMinSelectionIndex() == getMaxSelectionIndex())) {
                    // Find the end of the data codes.
                    int position = selectedPicturePosition + 1;
                    do {
                        pictureCode = pictureCodes.get(position++);
                    } while ((pictureCode != null) && pictureCode.isDataCode());
                   
                    // Only if there is at least one data code do we auto-select them.
                    int dataCodeCount = ((position - selectedIndex) - 1);
                    if (dataCodeCount > 0) {
                        setSelectionInterval(selectedIndex, position - 1);
                        return;
                    }
                }
               
                // This check is so that we don't redraw picture if picture is already at the position.
                if (selectedPicturePosition != picture.getPicturePosition()) {
                    picture.setPicturePosition(selectedPicturePosition);
                    picture.drawPicture();
                }
               
                // Auto-scroll the JList to show the selected picture code if it isn't visible.
                int minSelectionIndex = getMinSelectionIndex();
                if (minSelectionIndex < getFirstVisibleIndex() || minSelectionIndex > getLastVisibleIndex()) {
                    int numOfVisibleItems = (getLastVisibleIndex() - getFirstVisibleIndex()) - 1;
                    int topIndex = minSelectionIndex;
                    int bottomIndex = Math.min(minSelectionIndex + numOfVisibleItems, picture.getPictureCodes().size() + 1);
                    scrollRectToVisible(getCellBounds(topIndex, bottomIndex));
                }
               
            } else {
                // This takes care of moving the selection off the Start item. We don't want that
                // to be selectable.
                setSelectedIndex(1);
                return;
            }
        }
       
        // Keeps Picture in sync with currently selected items.
        picture.setSelectionInterval(getMinSelectionIndex() - 1, getMaxSelectionIndex() - 1);
    }
   
    /**
     * A CellRenderer that eliminates any of the overhead that the DefaultListCellRenderer (a JLabel)
     * adds.  Only left justified strings are displayed, and cells have a fixed preferred height and
     * width.  
     */
    class TextCellRenderer extends JPanel implements ListCellRenderer {
       
        String text;
        final int borderWidth = 2;
        final int baseline;
        final int width;
        final int height;

        /**
         * Constructor for TextCellRenderer.
         *
         * @param metrics FontMetrics from which we get info for drawing the text.
         * @param width The width of the renderer component.
         */
        TextCellRenderer(FontMetrics metrics, int width) {
            super();
            baseline = metrics.getAscent() + borderWidth;
            this.height = metrics.getHeight() + (2 * borderWidth);
            this.width = width;
            this.setFocusable(false);
        }

        /**
         * Return the renderers fixed size here. 
         */
        public Dimension getPreferredSize() {
            return new Dimension(width, height);
        }

        /**
         * Completely bypass all of the standard JComponent painting machinery.
         * This is a special case: the renderer is guaranteed to be opaque,
         * it has no children, and it's only a child of the JList while
         * it's being used to rubber stamp cells.
         * <p>
         * Clear the background and then draw the text.
         */
        public void paint(Graphics g) {
            g.setColor(getBackground());
            g.fillRect(0, 0, getWidth(), getHeight());
            g.setColor(getForeground());
            g.drawString(text, borderWidth, baseline);
        }


        /**
         * This is is the ListCellRenderer method.  It just sets the foreground and background
         * properties and updates the local text field.
         */
        public Component getListCellRendererComponent(
            JList list,
            Object value,
            int index,
            boolean isSelected,
            boolean cellHasFocus) {
           
            // TODO: The index gives us an opportunity to get hold of the PictureCode to get extra info.
           
            if (isSelected) {
                setBackground(list.getSelectionBackground());
            }
            else {
                setBackground(list.getBackground());
            }
           
            text = value.toString();
           
            if (text.startsWith("    ")) {
                if (isSelected) {
                    setForeground(Color.WHITE);
                    setBackground(Color.LIGHT_GRAY);
                } else {
                    setForeground(Color.GRAY);
                }
            }
            else {
                if (isSelected) {
                    setForeground(Color.WHITE);
                } else {
                    setForeground(Color.BLACK);
                }
            }
   
            return this;
        }
    }

    /**
     * Mouse Listener for the PictureCodeList JList. Handles the popup menu for items in the list.
     */
    class PictureCodeListMouseListener extends MouseAdapter {
     
        /**
         * Invoked when a mouse pressed event occurs on the PictureCodeList JList.
         *
         * @param e The MouseEvent.
         */
        public void mousePressed(MouseEvent e) {
            checkPopup(e);
        }
 
        /**
         * Invoked when a mouse released event occurs on the PictureCodeList JList.
         *
         * @param e The MouseEvent.
         */
        public void mouseReleased(MouseEvent e) {
            checkPopup(e);
        }
       
        /**
         * Checks if the mouse event is the popup event. This is the platform
         * independent way of implementing this.
         * 
         * @param e The MouseEvent to check.
         */
        private void checkPopup(MouseEvent e) {
            if (e.isPopupTrigger()) {
                // Convert the mouse event in to the corresponding picture position.
                int itemIndex = locationToIndex(e.getPoint());
               
                // Only show the popup menu if the item is selected and it isn't the end code.
                if ((itemIndex >= getMinSelectionIndex()) && (itemIndex <= getMaxSelectionIndex()) && !picture.getCurrentPictureCode().isEndCode()) {
                    popupMenu.refreshState();
                    popupMenu.show(PictureCodeList.this, e.getX(), e.getY());
                }
            }
        }
    }
   
    /**
     * Enum representing the options in the popup menu.
     */
    private enum PopupMenuAction {
        DELETE,
        COPY
    };
   
    /**
     * ActionListener for the popup menu that is activated for a selected item in the JList.
     */
    class PictureCodeListPopupMenuActionListener implements ActionListener {

        /**
         * Processes the given ActionEvent for the popup menu.
         *
         * @param e The ActionEvent to process.
         */
        public void actionPerformed(ActionEvent e) {
            PopupMenuAction action = PopupMenuAction.valueOf(e.getActionCommand().toUpperCase());
            switch (action) {
                case DELETE:
                    // Store some details about what is being deleted so we can decide what to do after it has been deleted.
                    int minSelectionIndex = getMinSelectionIndex();
                    PictureCode codeToDelete = picture.getCurrentPictureCode();

                    // Allow any code other than the end code to be deleted. There must always be an end code.
                    if (!codeToDelete.isEndCode()) {
                        // Remember, picture position is always one less than the JList index.
                        picture.deletePictureCodes(getMinSelectionIndex() - 1, getMaxSelectionIndex() - 1);
                    }
                   
                    // Reselect the current picture position after delete since JList will have lowered index by 1.
                    setSelectedIndex(minSelectionIndex);
                    break;
            }
        }
    }
}
TOP

Related Classes of com.agifans.picedit.gui.PictureCodeList$PictureCodeListMouseListener

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.