/*
* Beryl - A web platform based on XML, XSLT and Java
* This file is part of the Beryl XML GUI
*
* Copyright (C) 2004 Wenzel Jakob <wazlaf@tigris.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-3107 USA
*/
package org.beryl.gui.swing.plaf;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.IllegalComponentStateException;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Rectangle2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.AbstractAction;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JViewport;
import javax.swing.KeyStroke;
import javax.swing.ListModel;
import javax.swing.SwingUtilities;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.MouseInputAdapter;
import javax.swing.event.MouseInputListener;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.metal.MetalLookAndFeel;
import org.beryl.gui.swing.IconElement;
import org.beryl.gui.swing.JIconView;
/**
* JIconView - A simple Java icon view widget
* Portable look and feel implementation
* @version 2.0
* @author Wenzel Jakob
*/
public class IconViewUI extends ComponentUI {
private static final int UP = 1;
private static final int DOWN = 2;
private static final int LEFT = 3;
private static final int RIGHT = 4;
private JIconView view = null;
private MouseInputListener mouseListener = null;
private MouseMotionListener motionListener = null;
private FocusListener focusListener = null;
private ListSelectionListener selectionListener = null;
private ListDataListener dataListener = null;
private PropertyChangeListener changeListener = null;
private int oldIndex = -1, textHeight = 0;
private FontMetrics metrics = null;
private class DirectionAction extends AbstractAction {
private int direction = -1;
public DirectionAction(int direction, String name) {
super(name);
this.direction = direction;
}
public void actionPerformed(ActionEvent evt) {
int xcols = view.getSize().width / view.getXCellSize();
if (direction == RIGHT) {
view.setSelectedIndex(view.getSelectedIndex() + 1);
} else if (direction == LEFT) {
if (view.getSelectedIndex() != 0)
view.setSelectedIndex(view.getSelectedIndex() - 1);
} else if (direction == UP) {
if (view.getSelectedIndex() != xcols - 1)
view.setSelectedIndex(view.getSelectedIndex() - xcols);
} else if (direction == DOWN) {
view.setSelectedIndex(view.getSelectedIndex() + xcols);
}
}
}
/* ========== Handlers ========== */
private class MouseInputHandler extends MouseInputAdapter {
public void mousePressed(MouseEvent e) {
if (view.getModel() == null || !SwingUtilities.isLeftMouseButton(e))
return;
if (!view.hasFocus())
view.requestFocus();
int index = getIndexForRowColumn(convertYToRow(e.getY()), convertXToColumn(e.getX()));
if (index != -1 && checkBoundingBox(e.getX(), e.getY(), index))
view.setSelectedIndex(index);
else
view.setSelectedIndex(-1);
}
}
private class MouseMotionHandler extends MouseMotionAdapter {
public void mouseMoved(MouseEvent e) {
if (view.getModel() == null || (!view.getHandCursorEnabled()))
return;
int index = getIndexForRowColumn(convertYToRow(e.getY()), convertXToColumn(e.getX()));
if (index != -1 && checkBoundingBox(e.getX(), e.getY(), index))
view.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
else
view.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
}
}
private class FocusHandler implements FocusListener {
protected void repaintFocused() {
int index = view.getSelectedIndex();
if (index != -1)
view.repaint(getBounds(index, index));
}
public void focusGained(FocusEvent e) {
repaintFocused();
}
public void focusLost(FocusEvent e) {
repaintFocused();
}
}
private class ListSelectionHandler implements ListSelectionListener {
public void valueChanged(ListSelectionEvent e) {
int index = view.getSelectedIndex();
if (oldIndex != -1) {
view.repaint(getBounds(oldIndex, oldIndex));
}
if (index != -1) {
view.repaint(getBounds(index, index));
}
oldIndex = index;
}
}
private class ListDataHandler implements ListDataListener {
public void intervalAdded(ListDataEvent e) {
contentsChanged(e);
}
public void intervalRemoved(ListDataEvent e) {
contentsChanged(e);
}
public void contentsChanged(ListDataEvent e) {
int index = view.getSelectedIndex();
if (index != -1 && view.getModel().getSize() >= index) {
view.setSelectedIndex(-1);
}
view.repaint();
}
};
private class PropertyChangeHandler implements PropertyChangeListener {
public void propertyChange(PropertyChangeEvent event) {
String name = event.getPropertyName();
if (name.equals(JIconView.MODEL_PROPERTY)) {
ListModel model = (ListModel) event.getOldValue();
if (model != null)
model.removeListDataListener(dataListener);
model = (ListModel) event.getNewValue();
if (model != null) {
dataListener = new ListDataHandler();
model.addListDataListener(dataListener);
}
} else if (
name.equals(JIconView.XCELLSIZE_PROPERTY)
|| name.equals(JIconView.YCELLSIZE_PROPERTY)
|| name.equals(JIconView.ICONTEXTSPACING_PROPERTY)) {
view.repaint();
}
}
}
/* ========== UI functions ========== */
public IconViewUI() {
super();
}
public static ComponentUI createUI(JComponent c) {
return new IconViewUI();
}
public void installUI(JComponent component) {
view = (JIconView) component;
ListModel model = view.getModel();
if (model != null) {
dataListener = new ListDataHandler();
model.addListDataListener(dataListener);
}
changeListener = new PropertyChangeHandler();
mouseListener = new MouseInputHandler();
focusListener = new FocusHandler();
selectionListener = new ListSelectionHandler();
motionListener = new MouseMotionHandler();
view.addPropertyChangeListener(changeListener);
view.addMouseListener(mouseListener);
view.addFocusListener(focusListener);
view.addListSelectionListener(selectionListener);
view.addMouseMotionListener(motionListener);
view.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), "UP");
view.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), "DOWN");
view.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), "LEFT");
view.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), "RIGHT");
view.getActionMap().put("UP", new DirectionAction(UP, "UP"));
view.getActionMap().put("DOWN", new DirectionAction(DOWN, "DOWN"));
view.getActionMap().put("LEFT", new DirectionAction(LEFT, "LEFT"));
view.getActionMap().put("RIGHT", new DirectionAction(RIGHT, "RIGHT"));
}
public void uninstallUI(JComponent component) {
if (view != component)
throw new IllegalComponentStateException("could not uninstall component");
view.removePropertyChangeListener(changeListener);
view.removeFocusListener(focusListener);
view.removeMouseListener(mouseListener);
view.removeListSelectionListener(selectionListener);
view.removeMouseMotionListener(motionListener);
ListModel model = view.getModel();
if (model != null)
model.removeListDataListener(dataListener);
view.unregisterKeyboardAction(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0));
view.unregisterKeyboardAction(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0));
view.unregisterKeyboardAction(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0));
view.unregisterKeyboardAction(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0));
}
/* ========== Calculation functions ========== */
private int convertRowToY(int row) {
return view.getYCellSize() * row + view.getInsets().top;
}
private int convertColumnToX(int column) {
return view.getXCellSize() * column + view.getInsets().left;
}
private int getColumns() {
return (int) Math.floor(
(view.getSize().width - view.getInsets().left - view.getInsets().right) / (float) view.getXCellSize());
}
private int convertXToColumn(int x) {
return (int) Math.floor((x - view.getInsets().left) / (float) view.getXCellSize());
}
private int convertYToRow(int y) {
return (int) Math.floor((y - view.getInsets().top) / (float) view.getYCellSize());
}
private int getIndexForRowColumn(int row, int column) {
int xcols = getColumns();
int index = xcols * row + column;
if (column >= xcols || index >= view.getModel().getSize())
return -1;
return index;
}
private int getIndexForXY(int x, int y) {
return getIndexForRowColumn(convertYToRow(y), convertXToColumn(x));
}
private int[] getRowColumnForIndex(int index) {
int value[] = new int[2];
int xcols = getColumns();
value[0] = (int) Math.floor(index / (float) xcols);
value[1] = index - value[0] * xcols;
return value;
}
private Rectangle getBounds(int index1, int index2) {
int rc1[] = getRowColumnForIndex(Math.min(index1, index2));
int rc2[] = getRowColumnForIndex(Math.max(index1, index2));
int y1 = convertRowToY(rc1[0]), y2 = convertRowToY(rc2[0]);
int x1 = convertColumnToX(rc1[1]), x2 = convertColumnToX(rc2[1]);
if (y1 != y2) {
x1 = view.getInsets().left;
x2 = view.getSize().width - view.getInsets().right;
}
x2 += view.getXCellSize();
y2 += view.getYCellSize();
return new Rectangle(x1, y1, x2, y2);
}
private boolean checkBoundingBox(int x, int y, int index) {
IconElement icon = (IconElement) view.getModel().getElementAt(index);
int xcell = view.getXCellSize(), ycell = view.getYCellSize();
int iconWidth = icon.getIcon().getIconWidth();
int iconHeight = icon.getIcon().getIconHeight();
int spacing = view.getIconTextSpacing();
int stringWidth = metrics.stringWidth(icon.getText());
int center = ycell / 2 - (textHeight + iconHeight + spacing) / 2;
x = x % xcell;
y = y % ycell;
if (x > (xcell / 2 - iconWidth / 2)
&& x <= (xcell / 2 + iconWidth / 2)
&& y > center
&& y <= center + iconHeight + spacing + 1)
return true;
if (x > ((xcell - stringWidth) / 2)
&& x <= ((xcell + stringWidth) / 2 + 1)
&& y > (iconHeight + spacing + center + 1)
&& y <= textHeight + iconHeight + spacing + center + 3)
return true;
return false;
}
/* ========== Size functions ========== */
public Dimension getMinimumSize(JComponent component) {
return new Dimension(view.getXCellSize(), view.getYCellSize());
}
public Dimension getPreferredSize(JComponent component) {
/* Relations between width and height are not yet possible in
* swing, this is a functioning workaround */
Dimension dimension = new Dimension();
Dimension current = view.getSize();
Insets insets = view.getInsets();
if (view.getModel() != null) {
if (current.width == 0) {
int length = (int) Math.sqrt(view.getModel().getSize());
dimension.width = length * view.getXCellSize() + insets.left + insets.right;
dimension.height = length * view.getYCellSize() + insets.top + insets.bottom;
} else {
int width = view.getSize().width;
int xcells = (int) Math.floor((float) width / (float) view.getXCellSize());
if (xcells == 0)
xcells = 1;
int ycells = (int) Math.ceil((float) view.getModel().getSize() / (float) xcells);
int parentHeight = view.getParent().getSize().height;
dimension.width = xcells * view.getXCellSize() + insets.left + insets.right;
dimension.height = ycells * view.getYCellSize() + insets.top + insets.bottom;
if (parentHeight > dimension.height)
dimension.height = parentHeight;
}
} else {
return getMinimumSize(component);
}
return dimension;
}
/* ========== Paint ========== */
public void paint(Graphics g, JComponent component) {
if (component != view || view.getModel() == null)
return;
Graphics2D g2 = (Graphics2D) g;
ListModel model = view.getModel();
Rectangle paintBounds = g.getClipBounds();
Insets insets = view.getInsets();
Dimension size = view.getSize();
int modelSize = model.getSize();
int xcell = view.getXCellSize(), ycell = view.getYCellSize(), counter = 0;
metrics = g.getFontMetrics(view.getFont());
if (metrics == null) {
throw new RuntimeException("Could not resolve font metrics");
}
textHeight = metrics.getHeight();
/* Fix sizes */
if (paintBounds.x < insets.left)
paintBounds.x = insets.left;
if (paintBounds.width > size.width - insets.left - insets.right)
paintBounds.width = size.width - insets.left - insets.right;
if (paintBounds.y < insets.top)
paintBounds.y = insets.top;
if (view.getParent() instanceof JViewport) {
paintBounds.height += 2 * ycell;
if (paintBounds.height > size.height - insets.top - insets.bottom + ycell)
paintBounds.height = size.height - insets.top - insets.bottom + ycell;
} else {
if (paintBounds.height > size.height - insets.top - insets.bottom)
paintBounds.height = size.height - insets.top - insets.bottom;
}
/* Clear background */
g.setColor(view.getBackground());
g.fillRect(paintBounds.x, paintBounds.y, paintBounds.width, paintBounds.height);
g.setColor(view.getForeground());
int result[] = new int[(paintBounds.width / xcell) * (paintBounds.height / ycell)];
for (int y = paintBounds.y; y < paintBounds.y + paintBounds.height; y += ycell) {
for (int x = paintBounds.x; x <= paintBounds.x + paintBounds.width; x += xcell) {
int index = getIndexForXY(x, y);
if (index == -1)
continue;
int pos[] = getRowColumnForIndex(index);
int xpos = convertColumnToX(pos[1]);
int ypos = convertRowToY(pos[0]);
IconElement element = (IconElement) model.getElementAt(index);
ImageIcon icon = element.getIcon();
String name = element.getText();
int stringWidth = metrics.stringWidth(name);
int iconHeight = icon.getIconHeight();
int spacing = view.getIconTextSpacing();
int center = ycell / 2 - (textHeight + iconHeight + spacing) / 2;
if (stringWidth > xcell) {
while (stringWidth > xcell) {
name = name.substring(0, name.length() - 1);
stringWidth = metrics.stringWidth(name + "..");
}
name += "..";
}
g.drawImage(icon.getImage(), xpos + (xcell - icon.getIconWidth()) / 2, ypos + center, null);
if (view.getSelectedIndex() == index) {
if (view.hasFocus()) {
g2.setColor(Color.darkGray);
g2.setStroke(
new BasicStroke(
1.0f,
BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER,
1.0f,
new float[] { 1.0f },
0.0f));
g2.draw(
new Rectangle2D.Double(
xpos + (xcell - icon.getIconWidth()) / 2 - 1,
ypos + center - 2,
icon.getIconWidth() + 2,
iconHeight + 2));
g.setColor(MetalLookAndFeel.getTextHighlightColor());
} else {
g.setColor(MetalLookAndFeel.getControlDisabled());
}
g.fillRect(
xpos + (xcell - stringWidth) / 2,
ypos + iconHeight + 3 + center,
stringWidth + 2,
textHeight + 2);
}
g.setColor(view.getForeground());
g.drawString(name, xpos + (xcell - stringWidth) / 2, ypos + textHeight + iconHeight + spacing + center);
}
}
}
}