/*
* Copyright © 2010-2011 Rebecca G. Bettencourt / Kreative Software
* <p>
* The contents of this file are subject to the Mozilla Public License
* Version 1.1 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* <a href="http://www.mozilla.org/MPL/">http://www.mozilla.org/MPL/</a>
* <p>
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
* <p>
* Alternatively, the contents of this file may be used under the terms
* of the GNU Lesser General Public License (the "LGPL License"), in which
* case the provisions of LGPL License are applicable instead of those
* above. If you wish to allow use of your version of this file only
* under the terms of the LGPL License and not to allow others to use
* your version of this file under the MPL, indicate your decision by
* deleting the provisions above and replace them with the notice and
* other provisions required by the LGPL License. If you do not delete
* the provisions above, a recipient may use your version of this file
* under either the MPL or the LGPL License.
* @since PowerPaint 1.0
* @author Rebecca G. Bettencourt, Kreative Software
*/
package com.kreative.paint.swing;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dialog;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.Window;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import javax.swing.JComponent;
import javax.swing.Scrollable;
import javax.swing.SwingConstants;
public class CellSelector<T> extends JComponent implements Scrollable {
private static final long serialVersionUID = 1L;
private CellSelectorModel<T> model;
private CellSelectorRenderer<T> renderer;
private CellSelectionListener<T> listener;
private Dimension minsz, prefsz, maxsz;
public CellSelector(CellSelectorModel<T> model, CellSelectorRenderer<T> renderer) {
this.model = model;
this.renderer = renderer;
this.listener = new CellSelectionListener<T>() {
public void cellSelected(CellSelectionEvent<T> e) {
repaint();
}
};
this.minsz = this.prefsz = this.maxsz = null;
if (this.model != null) this.model.addCellSelectionListener(listener);
InternalMouseListener iml = new InternalMouseListener(false);
this.addMouseListener(iml);
this.addMouseMotionListener(iml);
}
public CellSelector(CellSelectorModel<T> model, CellSelectorRenderer<T> renderer, boolean popup) {
this.model = model;
this.renderer = renderer;
this.listener = new CellSelectionListener<T>() {
public void cellSelected(CellSelectionEvent<T> e) {
repaint();
}
};
this.minsz = this.prefsz = this.maxsz = null;
if (this.model != null) this.model.addCellSelectionListener(listener);
InternalMouseListener iml = new InternalMouseListener(popup);
this.addMouseListener(iml);
this.addMouseMotionListener(iml);
}
public CellSelector<T> asPopup() {
CellSelector<T> pop = new CellSelector<T>(model, renderer, true);
pop.minsz = this.minsz;
pop.prefsz = this.prefsz;
pop.maxsz = this.maxsz;
return pop;
}
public void pack() {
Component c = this;
c.invalidate();
while (c != null) {
if (c instanceof Window) { ((Window)c).pack(); break; }
else if (c instanceof Frame) { ((Frame)c).pack(); break; }
else if (c instanceof Dialog) { ((Dialog)c).pack(); break; }
else c = c.getParent();
}
}
public void dispose() {
if (this.model != null) this.model.removeCellSelectionListener(listener);
}
public CellSelectorModel<T> getModel() {
return model;
}
public void setModel(CellSelectorModel<T> model) {
if (this.model != null) this.model.removeCellSelectionListener(listener);
this.model = model;
if (this.model != null) this.model.addCellSelectionListener(listener);
}
public CellSelectorRenderer<T> getRenderer() {
return renderer;
}
public void setRenderer(CellSelectorRenderer<T> renderer) {
this.renderer = renderer;
}
public Dimension getMinimumSize() {
if (minsz != null) return minsz;
Insets i = getInsets();
int protocols = renderer.getColumns();
int protorows = renderer.getRows();
int cols = (protocols > 0) ? protocols : (protorows > 0) ? ((model.size() + protorows-1)/protorows) : optimumColumns(model.size());
int rows = (protorows > 0) ? protorows : (protocols > 0) ? ((model.size() + protocols-1)/protocols) : ((model.size() + cols-1)/cols);
return new Dimension(i.left + i.right + 7*cols + 3, i.top + i.bottom + 7*rows + 3);
}
public Dimension getPreferredScrollableViewportSize() {
if (prefsz != null) return prefsz;
Insets i = getInsets();
int protocols = renderer.getColumns();
int protorows = renderer.getRows();
int cols = (protocols > 0) ? protocols : (protorows > 0) ? ((model.size() + protorows-1)/protorows) : optimumColumns(model.size());
int rows = (protorows > 0) ? protorows : (protocols > 0) ? ((model.size() + protocols-1)/protocols) : ((model.size() + cols-1)/cols);
if (rows > 8) rows = 8;
int protocw = renderer.getCellWidth();
int protoch = renderer.getCellHeight();
int cw = (protocw > 0) ? protocw : (protoch > 0) ? protoch : 24;
int ch = (protoch > 0) ? protoch : (protocw > 0) ? protocw : 24;
return new Dimension(i.left + i.right + (5+cw)*cols + 3, i.top + i.bottom + (5+ch)*rows + 3);
}
public Dimension getPreferredSize() {
if (prefsz != null) return prefsz;
Insets i = getInsets();
int protocols = renderer.getColumns();
int protorows = renderer.getRows();
int cols = (protocols > 0) ? protocols : (protorows > 0) ? ((model.size() + protorows-1)/protorows) : optimumColumns(model.size());
int rows = (protorows > 0) ? protorows : (protocols > 0) ? ((model.size() + protocols-1)/protocols) : ((model.size() + cols-1)/cols);
int protocw = renderer.getCellWidth();
int protoch = renderer.getCellHeight();
int cw = (protocw > 0) ? protocw : (protoch > 0) ? protoch : 24;
int ch = (protoch > 0) ? protoch : (protocw > 0) ? protocw : 24;
return new Dimension(i.left + i.right + (5+cw)*cols + 3, i.top + i.bottom + (5+ch)*rows + 3);
}
public Dimension getMaximumSize() {
if (maxsz != null) return maxsz;
return super.getMaximumSize();
}
public void setMinimumSize(Dimension d) {
minsz = d;
}
public void setPreferredSize(Dimension d) {
prefsz = d;
}
public void setMaximumSize(Dimension d) {
maxsz = d;
}
public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
int ui = getScrollableUnitIncrement(visibleRect, orientation, direction);
return ui*(((orientation == SwingConstants.HORIZONTAL) ? visibleRect.width : visibleRect.height)/ui);
}
public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
Insets i = getInsets();
int protocols = renderer.getColumns();
int protorows = renderer.getRows();
int cols = (protocols > 0) ? protocols : (protorows > 0) ? ((model.size() + protorows-1)/protorows) : optimumColumns(model.size());
int rows = (protorows > 0) ? protorows : (protocols > 0) ? ((model.size() + protocols-1)/protocols) : ((model.size() + cols-1)/cols);
int cw = (((getWidth() - i.left - i.right - 3)/cols) - 5);
int ch = (((getHeight() - i.top - i.bottom - 3)/rows) - 5);
return ((orientation == SwingConstants.HORIZONTAL) ? cw : ch) + 5;
}
public boolean getScrollableTracksViewportHeight() {
return !renderer.isFixedHeight();
}
public boolean getScrollableTracksViewportWidth() {
return !renderer.isFixedWidth();
}
protected void paintComponent(Graphics g) {
Insets i = getInsets();
int protocols = renderer.getColumns();
int protorows = renderer.getRows();
int cols = (protocols > 0) ? protocols : (protorows > 0) ? ((model.size() + protorows-1)/protorows) : optimumColumns(model.size());
int rows = (protorows > 0) ? protorows : (protocols > 0) ? ((model.size() + protocols-1)/protocols) : ((model.size() + cols-1)/cols);
int cw = (((getWidth() - i.left - i.right - 3)/cols) - 5);
int ch = (((getHeight() - i.top - i.bottom - 3)/rows) - 5);
int s = model.getSelectedIndex();
int n = model.size();
g.clearRect(i.left, i.top, getWidth()-i.left-i.right, getHeight()-i.top-i.bottom);
for (int ly = 0, y = i.top+4, iy = 0; ly < rows && iy < n; ly++, y += ch+5, iy += cols) {
for (int lx = 0, x = i.left+4, ix = iy; lx < cols && ix < n; lx++, x += cw+5, ix++) {
if (ix == s) {
((Graphics2D)g).setPaint(Color.black);
g.fillRect(x-3, y-3, cw+6, ch+6);
((Graphics2D)g).setPaint(Color.lightGray);
g.fillRect(x-1, y-1, cw+2, ch+2);
} else {
((Graphics2D)g).setPaint(Color.gray);
g.fillRect(x-1, y-1, cw+2, ch+2);
}
renderer.paint(g, model.get(ix), x, y, cw, ch);
}
}
}
public int getIndexAt(int x, int y) {
Insets i = getInsets();
int protocols = renderer.getColumns();
int protorows = renderer.getRows();
int cols = (protocols > 0) ? protocols : (protorows > 0) ? ((model.size() + protorows-1)/protorows) : optimumColumns(model.size());
int rows = (protorows > 0) ? protorows : (protocols > 0) ? ((model.size() + protocols-1)/protocols) : ((model.size() + cols-1)/cols);
int cw = (((getWidth() - i.left - i.right - 3)/cols) - 5);
int ch = (((getHeight() - i.top - i.bottom - 3)/rows) - 5);
x -= i.left+2; x /= cw+5; if (x < 0) x = 0; if (x >= cols) x = cols-1;
y -= i.top+2; y /= ch+5; if (y < 0) y = 0; if (y >= rows) y = rows-1;
int idx = y*cols + x; if (idx < 0) idx = 0; if (idx >= model.size()) idx = model.size()-1;
return idx;
}
public T getObjectAt(int x, int y) {
return model.get(getIndexAt(x, y));
}
private class InternalMouseListener implements MouseListener, MouseMotionListener {
private boolean popup;
private T last;
public InternalMouseListener(boolean popup) {
this.popup = popup;
this.last = (model == null) ? null : model.getSelectedObject();
}
public void mouseEntered(MouseEvent e) {
if (model != null) {
this.last = model.getSelectedObject();
doHover(e);
}
}
public void mouseExited(MouseEvent e) {
if (model != null) {
doHover(e);
if (popup) model.setSelectedObject(this.last);
}
}
public void mouseMoved(MouseEvent e) { doHover(e); }
public void mouseClicked(MouseEvent e) { doClick(e); }
public void mouseDragged(MouseEvent e) { doClick(e); }
public void mousePressed(MouseEvent e) { doClick(e); }
public void mouseReleased(MouseEvent e) { doClick(e); }
private void doHover(MouseEvent e) {
if (model != null) {
int i = getIndexAt(e.getX(), e.getY());
setToolTipText(model.getToolTipText(i));
if (popup) model.setSelectedIndex(i);
}
}
private void doClick(MouseEvent e) {
if (model != null) {
int i = getIndexAt(e.getX(), e.getY());
model.setSelectedIndex(i);
this.last = model.get(i);
}
}
}
private static int optimumColumns(int n) {
if (n < 8) return n;
else {
int lower = (int)Math.ceil(Math.sqrt(n));
int upper = (int)Math.ceil(Math.sqrt(n) * 1.618034);
int cols = lower;
int diff = lower - (n % lower);
for (int i = lower; i <= upper; i++) {
if ((n % i) == 0) return i;
else if ((i - (n % i)) < diff) {
cols = i;
diff = i - (n % i);
}
}
return cols;
}
}
}