001: /*
002: * Beryl - A web platform based on XML, XSLT and Java
003: * This file is part of the Beryl XML GUI
004: *
005: * Copyright (C) 2004 Wenzel Jakob <wazlaf@tigris.org>
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011:
012: * This program is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this program; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-3107 USA
020: */
021:
022: package org.beryl.gui.swing.plaf;
023:
024: import java.awt.BasicStroke;
025: import java.awt.Color;
026: import java.awt.Cursor;
027: import java.awt.Dimension;
028: import java.awt.FontMetrics;
029: import java.awt.Graphics;
030: import java.awt.Graphics2D;
031: import java.awt.IllegalComponentStateException;
032: import java.awt.Insets;
033: import java.awt.Rectangle;
034: import java.awt.event.ActionEvent;
035: import java.awt.event.FocusEvent;
036: import java.awt.event.FocusListener;
037: import java.awt.event.KeyEvent;
038: import java.awt.event.MouseEvent;
039: import java.awt.event.MouseMotionAdapter;
040: import java.awt.event.MouseMotionListener;
041: import java.awt.geom.Rectangle2D;
042: import java.beans.PropertyChangeEvent;
043: import java.beans.PropertyChangeListener;
044:
045: import javax.swing.AbstractAction;
046: import javax.swing.ImageIcon;
047: import javax.swing.JComponent;
048: import javax.swing.JViewport;
049: import javax.swing.KeyStroke;
050: import javax.swing.ListModel;
051: import javax.swing.SwingUtilities;
052: import javax.swing.event.ListDataEvent;
053: import javax.swing.event.ListDataListener;
054: import javax.swing.event.ListSelectionEvent;
055: import javax.swing.event.ListSelectionListener;
056: import javax.swing.event.MouseInputAdapter;
057: import javax.swing.event.MouseInputListener;
058: import javax.swing.plaf.ComponentUI;
059: import javax.swing.plaf.metal.MetalLookAndFeel;
060:
061: import org.beryl.gui.swing.IconElement;
062: import org.beryl.gui.swing.JIconView;
063:
064: /**
065: * JIconView - A simple Java icon view widget
066: * Portable look and feel implementation
067: * @version 2.0
068: * @author Wenzel Jakob
069: */
070:
071: public class IconViewUI extends ComponentUI {
072: private static final int UP = 1;
073: private static final int DOWN = 2;
074: private static final int LEFT = 3;
075: private static final int RIGHT = 4;
076: private JIconView view = null;
077: private MouseInputListener mouseListener = null;
078: private MouseMotionListener motionListener = null;
079: private FocusListener focusListener = null;
080: private ListSelectionListener selectionListener = null;
081: private ListDataListener dataListener = null;
082: private PropertyChangeListener changeListener = null;
083: private int oldIndex = -1, textHeight = 0;
084: private FontMetrics metrics = null;
085:
086: private class DirectionAction extends AbstractAction {
087: private int direction = -1;
088:
089: public DirectionAction(int direction, String name) {
090: super (name);
091: this .direction = direction;
092: }
093:
094: public void actionPerformed(ActionEvent evt) {
095: int xcols = view.getSize().width / view.getXCellSize();
096:
097: if (direction == RIGHT) {
098: view.setSelectedIndex(view.getSelectedIndex() + 1);
099: } else if (direction == LEFT) {
100: if (view.getSelectedIndex() != 0)
101: view.setSelectedIndex(view.getSelectedIndex() - 1);
102: } else if (direction == UP) {
103: if (view.getSelectedIndex() != xcols - 1)
104: view.setSelectedIndex(view.getSelectedIndex()
105: - xcols);
106: } else if (direction == DOWN) {
107: view.setSelectedIndex(view.getSelectedIndex() + xcols);
108: }
109: }
110: }
111:
112: /* ========== Handlers ========== */
113:
114: private class MouseInputHandler extends MouseInputAdapter {
115: public void mousePressed(MouseEvent e) {
116: if (view.getModel() == null
117: || !SwingUtilities.isLeftMouseButton(e))
118: return;
119: if (!view.hasFocus())
120: view.requestFocus();
121:
122: int index = getIndexForRowColumn(convertYToRow(e.getY()),
123: convertXToColumn(e.getX()));
124: if (index != -1
125: && checkBoundingBox(e.getX(), e.getY(), index))
126: view.setSelectedIndex(index);
127: else
128: view.setSelectedIndex(-1);
129: }
130: }
131:
132: private class MouseMotionHandler extends MouseMotionAdapter {
133: public void mouseMoved(MouseEvent e) {
134: if (view.getModel() == null
135: || (!view.getHandCursorEnabled()))
136: return;
137:
138: int index = getIndexForRowColumn(convertYToRow(e.getY()),
139: convertXToColumn(e.getX()));
140: if (index != -1
141: && checkBoundingBox(e.getX(), e.getY(), index))
142: view.setCursor(Cursor
143: .getPredefinedCursor(Cursor.HAND_CURSOR));
144: else
145: view.setCursor(Cursor
146: .getPredefinedCursor(Cursor.DEFAULT_CURSOR));
147: }
148: }
149:
150: private class FocusHandler implements FocusListener {
151: protected void repaintFocused() {
152: int index = view.getSelectedIndex();
153: if (index != -1)
154: view.repaint(getBounds(index, index));
155: }
156:
157: public void focusGained(FocusEvent e) {
158: repaintFocused();
159: }
160:
161: public void focusLost(FocusEvent e) {
162: repaintFocused();
163: }
164: }
165:
166: private class ListSelectionHandler implements ListSelectionListener {
167: public void valueChanged(ListSelectionEvent e) {
168: int index = view.getSelectedIndex();
169: if (oldIndex != -1) {
170: view.repaint(getBounds(oldIndex, oldIndex));
171: }
172: if (index != -1) {
173: view.repaint(getBounds(index, index));
174: }
175: oldIndex = index;
176: }
177: }
178:
179: private class ListDataHandler implements ListDataListener {
180: public void intervalAdded(ListDataEvent e) {
181: contentsChanged(e);
182: }
183:
184: public void intervalRemoved(ListDataEvent e) {
185: contentsChanged(e);
186: }
187:
188: public void contentsChanged(ListDataEvent e) {
189: int index = view.getSelectedIndex();
190: if (index != -1 && view.getModel().getSize() >= index) {
191: view.setSelectedIndex(-1);
192: }
193: view.repaint();
194: }
195: };
196:
197: private class PropertyChangeHandler implements
198: PropertyChangeListener {
199: public void propertyChange(PropertyChangeEvent event) {
200: String name = event.getPropertyName();
201: if (name.equals(JIconView.MODEL_PROPERTY)) {
202: ListModel model = (ListModel) event.getOldValue();
203: if (model != null)
204: model.removeListDataListener(dataListener);
205: model = (ListModel) event.getNewValue();
206: if (model != null) {
207: dataListener = new ListDataHandler();
208: model.addListDataListener(dataListener);
209: }
210: } else if (name.equals(JIconView.XCELLSIZE_PROPERTY)
211: || name.equals(JIconView.YCELLSIZE_PROPERTY)
212: || name.equals(JIconView.ICONTEXTSPACING_PROPERTY)) {
213: view.repaint();
214: }
215: }
216: }
217:
218: /* ========== UI functions ========== */
219:
220: public IconViewUI() {
221: super ();
222: }
223:
224: public static ComponentUI createUI(JComponent c) {
225: return new IconViewUI();
226: }
227:
228: public void installUI(JComponent component) {
229: view = (JIconView) component;
230: ListModel model = view.getModel();
231: if (model != null) {
232: dataListener = new ListDataHandler();
233: model.addListDataListener(dataListener);
234: }
235: changeListener = new PropertyChangeHandler();
236: mouseListener = new MouseInputHandler();
237: focusListener = new FocusHandler();
238: selectionListener = new ListSelectionHandler();
239: motionListener = new MouseMotionHandler();
240: view.addPropertyChangeListener(changeListener);
241: view.addMouseListener(mouseListener);
242: view.addFocusListener(focusListener);
243: view.addListSelectionListener(selectionListener);
244: view.addMouseMotionListener(motionListener);
245: view.getInputMap().put(
246: KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), "UP");
247: view.getInputMap().put(
248: KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), "DOWN");
249: view.getInputMap().put(
250: KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), "LEFT");
251: view.getInputMap().put(
252: KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), "RIGHT");
253: view.getActionMap().put("UP", new DirectionAction(UP, "UP"));
254: view.getActionMap().put("DOWN",
255: new DirectionAction(DOWN, "DOWN"));
256: view.getActionMap().put("LEFT",
257: new DirectionAction(LEFT, "LEFT"));
258: view.getActionMap().put("RIGHT",
259: new DirectionAction(RIGHT, "RIGHT"));
260: }
261:
262: public void uninstallUI(JComponent component) {
263: if (view != component)
264: throw new IllegalComponentStateException(
265: "could not uninstall component");
266: view.removePropertyChangeListener(changeListener);
267: view.removeFocusListener(focusListener);
268: view.removeMouseListener(mouseListener);
269: view.removeListSelectionListener(selectionListener);
270: view.removeMouseMotionListener(motionListener);
271: ListModel model = view.getModel();
272: if (model != null)
273: model.removeListDataListener(dataListener);
274: view.unregisterKeyboardAction(KeyStroke.getKeyStroke(
275: KeyEvent.VK_UP, 0));
276: view.unregisterKeyboardAction(KeyStroke.getKeyStroke(
277: KeyEvent.VK_DOWN, 0));
278: view.unregisterKeyboardAction(KeyStroke.getKeyStroke(
279: KeyEvent.VK_LEFT, 0));
280: view.unregisterKeyboardAction(KeyStroke.getKeyStroke(
281: KeyEvent.VK_RIGHT, 0));
282: }
283:
284: /* ========== Calculation functions ========== */
285:
286: private int convertRowToY(int row) {
287: return view.getYCellSize() * row + view.getInsets().top;
288: }
289:
290: private int convertColumnToX(int column) {
291: return view.getXCellSize() * column + view.getInsets().left;
292: }
293:
294: private int getColumns() {
295: return (int) Math.floor((view.getSize().width
296: - view.getInsets().left - view.getInsets().right)
297: / (float) view.getXCellSize());
298: }
299:
300: private int convertXToColumn(int x) {
301: return (int) Math.floor((x - view.getInsets().left)
302: / (float) view.getXCellSize());
303: }
304:
305: private int convertYToRow(int y) {
306: return (int) Math.floor((y - view.getInsets().top)
307: / (float) view.getYCellSize());
308: }
309:
310: private int getIndexForRowColumn(int row, int column) {
311: int xcols = getColumns();
312: int index = xcols * row + column;
313: if (column >= xcols || index >= view.getModel().getSize())
314: return -1;
315: return index;
316: }
317:
318: private int getIndexForXY(int x, int y) {
319: return getIndexForRowColumn(convertYToRow(y),
320: convertXToColumn(x));
321: }
322:
323: private int[] getRowColumnForIndex(int index) {
324: int value[] = new int[2];
325: int xcols = getColumns();
326: value[0] = (int) Math.floor(index / (float) xcols);
327: value[1] = index - value[0] * xcols;
328: return value;
329: }
330:
331: private Rectangle getBounds(int index1, int index2) {
332: int rc1[] = getRowColumnForIndex(Math.min(index1, index2));
333: int rc2[] = getRowColumnForIndex(Math.max(index1, index2));
334: int y1 = convertRowToY(rc1[0]), y2 = convertRowToY(rc2[0]);
335: int x1 = convertColumnToX(rc1[1]), x2 = convertColumnToX(rc2[1]);
336:
337: if (y1 != y2) {
338: x1 = view.getInsets().left;
339: x2 = view.getSize().width - view.getInsets().right;
340: }
341: x2 += view.getXCellSize();
342: y2 += view.getYCellSize();
343: return new Rectangle(x1, y1, x2, y2);
344: }
345:
346: private boolean checkBoundingBox(int x, int y, int index) {
347: IconElement icon = (IconElement) view.getModel().getElementAt(
348: index);
349: int xcell = view.getXCellSize(), ycell = view.getYCellSize();
350: int iconWidth = icon.getIcon().getIconWidth();
351: int iconHeight = icon.getIcon().getIconHeight();
352: int spacing = view.getIconTextSpacing();
353: int stringWidth = metrics.stringWidth(icon.getText());
354: int center = ycell / 2 - (textHeight + iconHeight + spacing)
355: / 2;
356: x = x % xcell;
357: y = y % ycell;
358:
359: if (x > (xcell / 2 - iconWidth / 2)
360: && x <= (xcell / 2 + iconWidth / 2) && y > center
361: && y <= center + iconHeight + spacing + 1)
362: return true;
363: if (x > ((xcell - stringWidth) / 2)
364: && x <= ((xcell + stringWidth) / 2 + 1)
365: && y > (iconHeight + spacing + center + 1)
366: && y <= textHeight + iconHeight + spacing + center + 3)
367: return true;
368: return false;
369: }
370:
371: /* ========== Size functions ========== */
372:
373: public Dimension getMinimumSize(JComponent component) {
374: return new Dimension(view.getXCellSize(), view.getYCellSize());
375: }
376:
377: public Dimension getPreferredSize(JComponent component) {
378: /* Relations between width and height are not yet possible in
379: * swing, this is a functioning workaround */
380: Dimension dimension = new Dimension();
381: Dimension current = view.getSize();
382: Insets insets = view.getInsets();
383: if (view.getModel() != null) {
384: if (current.width == 0) {
385: int length = (int) Math.sqrt(view.getModel().getSize());
386:
387: dimension.width = length * view.getXCellSize()
388: + insets.left + insets.right;
389: dimension.height = length * view.getYCellSize()
390: + insets.top + insets.bottom;
391: } else {
392: int width = view.getSize().width;
393: int xcells = (int) Math.floor((float) width
394: / (float) view.getXCellSize());
395: if (xcells == 0)
396: xcells = 1;
397: int ycells = (int) Math.ceil((float) view.getModel()
398: .getSize()
399: / (float) xcells);
400:
401: int parentHeight = view.getParent().getSize().height;
402: dimension.width = xcells * view.getXCellSize()
403: + insets.left + insets.right;
404: dimension.height = ycells * view.getYCellSize()
405: + insets.top + insets.bottom;
406: if (parentHeight > dimension.height)
407: dimension.height = parentHeight;
408: }
409: } else {
410: return getMinimumSize(component);
411: }
412: return dimension;
413: }
414:
415: /* ========== Paint ========== */
416:
417: public void paint(Graphics g, JComponent component) {
418: if (component != view || view.getModel() == null)
419: return;
420: Graphics2D g2 = (Graphics2D) g;
421:
422: ListModel model = view.getModel();
423: Rectangle paintBounds = g.getClipBounds();
424: Insets insets = view.getInsets();
425: Dimension size = view.getSize();
426: int modelSize = model.getSize();
427: int xcell = view.getXCellSize(), ycell = view.getYCellSize(), counter = 0;
428: metrics = g.getFontMetrics(view.getFont());
429:
430: if (metrics == null) {
431: throw new RuntimeException("Could not resolve font metrics");
432: }
433:
434: textHeight = metrics.getHeight();
435:
436: /* Fix sizes */
437: if (paintBounds.x < insets.left)
438: paintBounds.x = insets.left;
439: if (paintBounds.width > size.width - insets.left - insets.right)
440: paintBounds.width = size.width - insets.left - insets.right;
441: if (paintBounds.y < insets.top)
442: paintBounds.y = insets.top;
443: if (view.getParent() instanceof JViewport) {
444: paintBounds.height += 2 * ycell;
445: if (paintBounds.height > size.height - insets.top
446: - insets.bottom + ycell)
447: paintBounds.height = size.height - insets.top
448: - insets.bottom + ycell;
449: } else {
450: if (paintBounds.height > size.height - insets.top
451: - insets.bottom)
452: paintBounds.height = size.height - insets.top
453: - insets.bottom;
454: }
455:
456: /* Clear background */
457: g.setColor(view.getBackground());
458: g.fillRect(paintBounds.x, paintBounds.y, paintBounds.width,
459: paintBounds.height);
460: g.setColor(view.getForeground());
461:
462: int result[] = new int[(paintBounds.width / xcell)
463: * (paintBounds.height / ycell)];
464: for (int y = paintBounds.y; y < paintBounds.y
465: + paintBounds.height; y += ycell) {
466: for (int x = paintBounds.x; x <= paintBounds.x
467: + paintBounds.width; x += xcell) {
468: int index = getIndexForXY(x, y);
469: if (index == -1)
470: continue;
471: int pos[] = getRowColumnForIndex(index);
472: int xpos = convertColumnToX(pos[1]);
473: int ypos = convertRowToY(pos[0]);
474: IconElement element = (IconElement) model
475: .getElementAt(index);
476: ImageIcon icon = element.getIcon();
477: String name = element.getText();
478: int stringWidth = metrics.stringWidth(name);
479: int iconHeight = icon.getIconHeight();
480: int spacing = view.getIconTextSpacing();
481: int center = ycell / 2
482: - (textHeight + iconHeight + spacing) / 2;
483:
484: if (stringWidth > xcell) {
485: while (stringWidth > xcell) {
486: name = name.substring(0, name.length() - 1);
487: stringWidth = metrics.stringWidth(name + "..");
488: }
489: name += "..";
490: }
491:
492: g.drawImage(icon.getImage(), xpos
493: + (xcell - icon.getIconWidth()) / 2, ypos
494: + center, null);
495: if (view.getSelectedIndex() == index) {
496: if (view.hasFocus()) {
497: g2.setColor(Color.darkGray);
498: g2.setStroke(new BasicStroke(1.0f,
499: BasicStroke.CAP_BUTT,
500: BasicStroke.JOIN_MITER, 1.0f,
501: new float[] { 1.0f }, 0.0f));
502: g2
503: .draw(new Rectangle2D.Double(xpos
504: + (xcell - icon.getIconWidth())
505: / 2 - 1, ypos + center - 2,
506: icon.getIconWidth() + 2,
507: iconHeight + 2));
508: g.setColor(MetalLookAndFeel
509: .getTextHighlightColor());
510: } else {
511: g.setColor(MetalLookAndFeel
512: .getControlDisabled());
513: }
514: g.fillRect(xpos + (xcell - stringWidth) / 2, ypos
515: + iconHeight + 3 + center, stringWidth + 2,
516: textHeight + 2);
517: }
518: g.setColor(view.getForeground());
519: g.drawString(name, xpos + (xcell - stringWidth) / 2,
520: ypos + textHeight + iconHeight + spacing
521: + center);
522: }
523: }
524: }
525: }
|