/* * 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. */ /* * IntellicutListRenderer.java * Creation date: Jun 16, 2003 * By: Frank Worsley */ package org.openquark.gems.client; import java.awt.Color; import java.awt.Component; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Insets; import java.awt.RenderingHints; import java.util.HashMap; import java.util.Map; import javax.swing.DefaultListCellRenderer; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JList; import javax.swing.SwingUtilities; import org.openquark.cal.services.GemEntity; import org.openquark.gems.client.AutoburnLogic.AutoburnUnifyStatus; import org.openquark.gems.client.IntellicutListModelAdapter.IntellicutListEntry; import org.openquark.gems.client.IntellicutManager.IntellicutInfo; import org.openquark.gems.client.IntellicutManager.IntellicutMode; import org.openquark.util.ui.UIUtilities; /** * The cell renderer for the intellicut list. * * @author Frank Worsley */ public class IntellicutListRenderer extends DefaultListCellRenderer { private static final long serialVersionUID = -9074143759683342266L; /** The background color of list items that are not selected. */ static final Color NORMAL_BACKGROUND_COLOR = Color.WHITE; /** The background color of list items that are selected. */ static final Color SELECTED_BACKGROUND_COLOR = Color.BLUE.darker().darker(); /** The background color if the list item is transparent and not selected. */ static final Color TRANSPARENT_BACKGROUND_COLOR = new Color(0, 0, 0, 0); /** The background color if the list item is transparent and selected. */ static final Color SELECTED_TRANSPARENT_BACKGROUND_COLOR = new Color(100, 150, 255, 100); /** The text highlight colour for non selected items. */ static final Color HIGHLIGHT_COLOR = Color.MAGENTA.darker(); /** The text highlight colour for selected items. */ static final Color SELECTED_HIGHTLIGHT_COLOR = Color.ORANGE; /** The text color for normal items. */ static final Color TEXT_COLOR = Color.BLACK; /** The text color for selected items. */ static final Color SELECTED_TEXT_COLOR = Color.WHITE; /** The text color for transparent items. */ static final Color TRANSPARENT_TEXT_COLOR = Color.BLACK; /** The text color for selected transparent items. */ static final Color SELECTED_TRANSPARENT_TEXT_COLOR = Color.WHITE; /** The outline color for transparent non selected items. */ static final Color OUTLINE_COLOR = new Color(250, 250, 250, 100); /** The outline color for transparent selected items. */ static final Color SELECTED_OUTLINE_COLOR = new Color(80, 130, 255, 100); /** The size of the glowing outline drawn around text if the list is transparent. */ private static final int OUTLINE_SIZE = 2; /** The list entry this renderer is rendering. */ private IntellicutListEntry listEntry; /** * The list this renderer is used for. This may not be an IntellicutList since * this renderer is also used for the normal JList of the IntellicutComboBox. */ private JList intellicutList; /** Whether or not this item is selected. */ private boolean selected; /** Whether or not this item is transparent. */ private boolean transparent; /** Scope decal */ private static final ImageIcon SCOPE_PRIVATE_ICON = new ImageIcon(TableTop.class.getResource("/Resources/intellicut_private.gif")); private static final ImageIcon SCOPE_PROTECTED_ICON = new ImageIcon(TableTop.class.getResource("/Resources/intellicut_protected.gif")); private static final ImageIcon SCOPE_PUBLIC_ICON = new ImageIcon(TableTop.class.getResource("/Resources/intellicut_public.gif")); // Icons for when only displaying scope icons private ImageIcon emitterIcon = null; private ImageIcon reflectorIcon = null; private ImageIcon scopePrivateIcon = null; private ImageIcon scopeProtectedIcon = null; private ImageIcon scopePublicIcon = null; // Icons for when displaying both burning status and scope private ImageIcon burnImageIcon = null; private ImageIcon burnQuestionIcon = null; private ImageIcon blankImageIcon = null; private ImageIcon emitterIcon_shifted = null; private ImageIcon reflectorIcon_shifted = null; private ImageIcon scopePrivateIcon_shifted = null; private ImageIcon scopeProtectedIcon_shifted = null; private ImageIcon scopePublicIcon_shifted = null; /** Cache for the scope decorated icons */ private final Map<Icon, Icon> imageCache_public = new HashMap<Icon, Icon>(); private final Map<Icon, Icon> imageCache_protected = new HashMap<Icon, Icon>(); private final Map<Icon, Icon> imageCache_private = new HashMap<Icon, Icon>(); /** Where the intellicut is activated */ private final IntellicutMode intellicutMode; /** * @param list the IntellicutList the renderer will be used for * @param value the list entry item that should be rendered * @param index the index of the item in the model * @param isSelected true if the item is selected * @param cellHasFocus true if the item has focus * @return Component the component to use for rendering the list cell with the given values. */ @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { this.listEntry = (IntellicutListEntry) value; this.intellicutList = list; this.selected = isSelected; if (list instanceof IntellicutList) { transparent = ((IntellicutList) list).isTransparent(); } else { // If the list is actually the JList in the IntellicutComboBox transparent = false; } setText(listEntry.getDisplayString()); // Choose the correct background color if (transparent) { setBackground(selected ? SELECTED_TRANSPARENT_BACKGROUND_COLOR : TRANSPARENT_BACKGROUND_COLOR); } else { setBackground(selected ? SELECTED_BACKGROUND_COLOR : NORMAL_BACKGROUND_COLOR); } // Looks better with a narrower gap (default is 4) setIconTextGap(2); setIcon(getIconForEntry(listEntry)); return this; } /** Constructor */ public IntellicutListRenderer(IntellicutMode mode) { createIcons(); intellicutMode = mode; } /** * Creates images for the icons used by this list */ private void createIcons() { // Icons for when only displaying scope icons emitterIcon = createCenteredIcon(TableTopPanel.emitterImageIconSmall); reflectorIcon = createCenteredIcon(TableTopPanel.reflectorImageIconSmall); scopePrivateIcon = createCenteredIcon (SCOPE_PRIVATE_ICON); scopeProtectedIcon = createCenteredIcon (SCOPE_PROTECTED_ICON); scopePublicIcon = createCenteredIcon (SCOPE_PUBLIC_ICON); // Icons for when displaying both burning status and scope blankImageIcon = createCroppedAndCenteredIcon(TableTopPanel.blankImageIconSmall); burnImageIcon = createCroppedAndCenteredIcon(TableTopPanel.burnImageIconSmall); burnQuestionIcon = createCroppedAndCenteredIcon(TableTopPanel.burnQuestionImageIconSmall); emitterIcon_shifted = createShiftedIcon(TableTopPanel.emitterImageIconSmall); reflectorIcon_shifted = createShiftedIcon(TableTopPanel.reflectorImageIconSmall); scopePrivateIcon_shifted = createShiftedIcon (SCOPE_PRIVATE_ICON); scopeProtectedIcon_shifted = createShiftedIcon (SCOPE_PROTECTED_ICON); scopePublicIcon_shifted = createShiftedIcon (SCOPE_PUBLIC_ICON); } /** * Takes a small icon image, and vertically centers it so that the new icon fits within the dimensions of a row label. * @param icon * @return new icon */ private ImageIcon createCenteredIcon(ImageIcon icon) { int height = this.getFontMetrics(this.getFont()).getHeight(); // The image will be shifted down so that it is vertically centered, then padded at the bottom to match the required height Image newImage = UIUtilities.cropImage(UIUtilities.shiftImage(icon.getImage(), 0, ((height - icon.getIconHeight())/2 - 1)), 0, 0, 0, -1 * ((height - icon.getIconHeight())/2 + 1)); return new ImageIcon(newImage); } /** * Takes a small icon image, vertically centers it so that the new icon fits within the dimensions of a row label. * Then crop 2 pixels on the left side of the image for better appearance * @param icon * @return new icon */ private ImageIcon createCroppedAndCenteredIcon(ImageIcon icon) { int height = this.getFontMetrics(this.getFont()).getHeight(); // The image will be shifted down so that it is vertically centered, then padded at the bottom to match the required height Image newImage = UIUtilities.cropImage(UIUtilities.shiftImage(icon.getImage(), 0, ((height - icon.getIconHeight())/2 - 1)), 2, 0, 0, -1 * ((height - icon.getIconHeight())/2 + 1)); return new ImageIcon(newImage); } /** * Takes a small icon image and make room on the left for combining with the burning status icons. * @param icon * @return new icon the icon that has enough padding on the left to fit another icon */ private ImageIcon createShiftedIcon(ImageIcon icon) { Image newImage = UIUtilities.cropImage(icon.getImage(), -10, 0, 0, 0 ); return new ImageIcon(newImage); } /** * Determines the burning icon that should be used for a list entry. * @param listEntry the intellicut entry to get the icon for * @return the burning icon to use or a blank image for no burning icon */ private Icon getBurnImageForEntry(IntellicutListEntry listEntry) { IntellicutInfo intellicutInfo = listEntry.getIntellicutInfo(); AutoburnUnifyStatus burnStatus = intellicutInfo.getAutoburnUnifyStatus(); if (burnStatus == AutoburnUnifyStatus.UNAMBIGUOUS) { return burnImageIcon; } else if (burnStatus == AutoburnUnifyStatus.NOT_NECESSARY) { return blankImageIcon; } else if (burnStatus == AutoburnUnifyStatus.AMBIGUOUS) { return burnQuestionIcon; } else if (burnStatus == AutoburnUnifyStatus.UNAMBIGUOUS_NOT_NECESSARY) { if (intellicutInfo.getBurnTypeCloseness() > intellicutInfo.getNoBurnTypeCloseness()) { return burnImageIcon; } } else if (burnStatus == AutoburnUnifyStatus.AMBIGUOUS_NOT_NECESSARY) { if (intellicutInfo.getBurnTypeCloseness() > intellicutInfo.getNoBurnTypeCloseness()) { return burnQuestionIcon; } } return blankImageIcon; } /** * Determines the icon for the entry. The scoped entry icon could be a scope icon only or scope icon plus burn status icon, * depending on which part the intellicut was activated on. * @param listEntry the intellicut entry to get the icon for * @return icon with scope (and burn status) or the appropriate icon for non-scoped entry */ public Icon getIconForEntry(IntellicutListEntry listEntry) { // Overlays the burn status image and scope icon only if intellicut is activated on an input part if (intellicutMode == IntellicutMode.PART_INPUT) { return getIconWithBurnStatus(listEntry); } else {// (intellicutMode == IntellicutMode.PART_OUTPUT || intellicutMode == IntellicutMode.NOTHING return getIconWithoutBurnStatus(listEntry); } } /** * Determines the icon for the entry where the left column is the burn status icon and the right column is the scope icon. * Non-scoped entry returns the appropriate gem type icon. * @param listEntry * @return Icon an combined icon with burn status and scope */ private Icon getIconWithBurnStatus(IntellicutListEntry listEntry) { Object listData = listEntry.getData(); // Retrieve the top layer of the icon Icon overlayImage = getBurnImageForEntry(listEntry); // Add scope decal (public, protected, private) underneath if entry is scoped if (listData instanceof GemEntity) { // Uses cache to store icons already combined GemEntity entity = (GemEntity) listData; Icon baseGemIcon = null; Icon overlayIcon = overlayImage; Map<Icon, Icon> cache = null; if (entity.getScope().isPublic()) { cache = imageCache_public; baseGemIcon = scopePublicIcon_shifted; } else if (entity.getScope().isProtected()) { cache = imageCache_protected; baseGemIcon = scopeProtectedIcon_shifted; } else { // entity.getScope().isPrivate() cache = imageCache_private; baseGemIcon = scopePrivateIcon_shifted; } Icon cachedImage = cache.get(overlayIcon); if (cachedImage != null) { return cachedImage; } else { Icon newIcon = UIUtilities.combineIcons(baseGemIcon, overlayIcon); cache.put(overlayIcon, newIcon); return newIcon; } } else if (listData instanceof CollectorGem) { return ((CollectorGem) listData).getReflectedInputs().isEmpty() ? emitterIcon_shifted : reflectorIcon_shifted; } return blankImageIcon; } /** * Determines the icon for the list entry without any burn status icons. Non-scoped entry returns the appropriate gem type icon. * @param listEntry * @return Icon Either a scope icon or the appropriate gem type icon */ private Icon getIconWithoutBurnStatus(IntellicutListEntry listEntry) { Object listData = listEntry.getData(); if (listData instanceof GemEntity) { GemEntity entity = (GemEntity) listData; if (entity.getScope().isPublic()) { return scopePublicIcon; } else if (entity.getScope().isProtected()) { return scopeProtectedIcon; } else { return scopePrivateIcon; } } else if (listData instanceof CollectorGem) { return ((CollectorGem) listData).getReflectedInputs().isEmpty() ? emitterIcon : reflectorIcon; } return blankImageIcon; } /** * This is needed so that drawing the label works correctly. We have to always * fill in our background so that the list updates correctly if we scroll. * @param g the graphics object to draw with */ @Override public void paintComponent(Graphics g) { g.setColor(getBackground()); g.fillRect(0, 0, getWidth(), getHeight()); super.paintComponent(g); } /** * Overloaded paint function to draw our custom highlighted text. * @param g the graphics object to draw with */ @Override public void paint(Graphics g) { Insets insets = getInsets(); Icon icon = getIcon(); int iconWidth = icon != null ? icon.getIconWidth() : 0; String text1 = listEntry.getDisplayString(); String text2 = " (" + listEntry.getSecondaryDisplayString() + ")"; if (text2.length() == 3) { text2 = ""; } Graphics2D g2 = (Graphics2D) g; ///////////////////// // TODO: Module name should be de-emphasized from regular name, // but still attract attention. The following variables are // here to assist in selection of this font. Font textFont = getFont().deriveFont(Font.PLAIN ).deriveFont(getFont().getSize2D()-0.0f); Font text2Font = (getFont().deriveFont( Font.PLAIN ).deriveFont(getFont().getSize2D()-0.0f)); //LinkedHashMap attributes = new LinkedHashMap(); //attributes.put(TextAttribute.WIDTH, TextAttribute.WIDTH_CONDENSED); //attributes.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_ULTRABOLD ); //text2Font = text2Font.deriveFont(attributes); //text2Font = text2Font.deriveFont(new AffineTransform(0.9,0.0,0.0,1.0,0.0,0.0)); Object textRenderHint = (RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); Object text2RenderHint = (RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); Color text2Color = selected ? SELECTED_TEXT_COLOR : new Color(10, 10, 10, 190); Color outline2Color = selected ? SELECTED_OUTLINE_COLOR : OUTLINE_COLOR;//new Color(240, 240, 240, 100); // 100 //////////////////// // draw in the background g2.setColor(getBackground()); g2.fillRect(0, 0, getWidth(), getHeight()); // draw in the icon if (icon != null) { icon.paintIcon(this, g2, insets.left, insets.top); } // figure out the text color Color textColor = selected ? SELECTED_TEXT_COLOR : TEXT_COLOR; if (transparent) { textColor = selected ? SELECTED_TRANSPARENT_TEXT_COLOR : TRANSPARENT_TEXT_COLOR; } // get ready to draw the text g2.setFont(textFont); g2.setColor(textColor); final int xOffset = insets.left + iconWidth + getIconTextGap(); final int yOffset = getHeight() - getFontMetrics(textFont).getDescent() - insets.bottom + OUTLINE_SIZE; final int xOffset2 = xOffset + SwingUtilities.computeStringWidth(getFontMetrics(textFont), text1+" "); // draw an outline for non-selected text if the item is transparent. if (transparent) { Color outlineColor = selected ? SELECTED_OUTLINE_COLOR : OUTLINE_COLOR; g2.setColor(outlineColor); g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); for (int x = 0, xmax = 2 * OUTLINE_SIZE; x <= xmax; x++) { for (int y = 0, ymax = 2 * OUTLINE_SIZE; y <= ymax; y++) { g2.drawString(text1, xOffset + x, yOffset - y); } } g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); g2.setColor(outline2Color); g2.setFont(text2Font); for (int x = 0, xmax = 0+2*OUTLINE_SIZE; x <= xmax; x++) { for (int y = 0, ymax = 0+2*OUTLINE_SIZE; y <= ymax; y++) { g2.drawString(text2, xOffset2 + x, yOffset - y); } } g2.setFont(textFont); g2.setColor(textColor); g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, textRenderHint); } // draw the main string g2.drawString(text1, xOffset + OUTLINE_SIZE, yOffset - OUTLINE_SIZE); g2.setColor(text2Color); g2.setFont(text2Font); g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, text2RenderHint); g2.drawString(text2, xOffset2 + OUTLINE_SIZE, yOffset - OUTLINE_SIZE); g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); g2.setFont(textFont); g2.setColor(textColor); // draw the letters the user has entered in a different color String userInput = ((IntellicutListModel) intellicutList.getModel()).getUserInput(); String textToMatch = listEntry.getDisplayString(); if (userInput.length() > 0 && userInput.length() <= textToMatch.length() && textToMatch.substring(0, userInput.length()).equalsIgnoreCase(userInput)) { Color typedCharactersColor = selected ? SELECTED_HIGHTLIGHT_COLOR : HIGHLIGHT_COLOR; g2.setColor(typedCharactersColor); int index = text1.length() - textToMatch.length(); int prefixWidth = 0; if (index > 0) { String prefix = getText().substring(0, index); prefixWidth = SwingUtilities.computeStringWidth(getFontMetrics(getFont()), prefix); } String highlight = text1.substring(index, index + userInput.length()); g2.drawString(highlight, xOffset + OUTLINE_SIZE + prefixWidth, yOffset - OUTLINE_SIZE); } } }