/*
* File : ThumbsExplorerPanel.java
* Created : 11-feb-2004 11:44
* By : fbusquets
*
* JClic - Authoring and playing system for educational activities
*
* Copyright (C) 2000 - 2005 Francesc Busquets & Departament
* d'Educacio de la Generalitat de Catalunya
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 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 General Public License for more details (see the LICENSE file).
*/
package edu.xtec.jclic.boxes;
import edu.xtec.util.LFUtil;
import java.awt.AWTEvent;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.font.FontRenderContext;
import java.awt.font.TextHitInfo;
import java.awt.font.TextLayout;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.List;
import javax.swing.ImageIcon;
import javax.swing.JPanel;
import javax.swing.event.EventListenerList;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
/**
* This class is a {@link javax.swing.JPanel} that contains a set of
* {@link edu.xtec.jclic.boxes.ThumbsExplorerPanel.ThumbElement} objects. Elements are
* displayed into a grid of rectangular cells of same size, distributed from left to
* right and top to bottom. It provides methods to select a specific ThumbElement, and
* has a {@link javax.swing.event.EventListenerList} used to notify observers about
* selection changes.
* @author Francesc Busquets (fbusquets@xtec.cat)
* @version 13.08.13
*/
public class ThumbsExplorerPanel extends JPanel {
public static final int DEFAULT_THUMB_WIDTH=90;
public static final int DEFAULT_THUMB_HEIGHT=90;
public static final int DEFAULT_THUMB_MARGIN=14;
public static final int DEFAULT_TEXT_HEIGHT=14;
public static final int DEFAULT_THUMB_INTERNAL_MARGIN=2;
protected static BasicStroke BORDER_STROKE=new BasicStroke(0.2F);
protected int th_width, th_height, th_margin, th_textHeight, th_int_margin;
protected Font font;
private List<ThumbElement> elements;
private ThumbElement current;
private int elementsPerRow;
private EventListenerList listenersList;
public Dimension maxThumbSize;
public Dimension boxSize;
/** Creates a new instance of ThumbsExplorerPanel */
public ThumbsExplorerPanel() {
setLayout(null);
elements=new ArrayList<ThumbElement>();
listenersList=new EventListenerList();
setSizes(DEFAULT_THUMB_WIDTH, DEFAULT_THUMB_HEIGHT,
DEFAULT_THUMB_MARGIN, DEFAULT_TEXT_HEIGHT, DEFAULT_THUMB_INTERNAL_MARGIN);
setPreferredSize(new Dimension(3*boxSize.width, boxSize.height));
setFont(new Font("Dialog", Font.PLAIN, 12));
setBackground(LFUtil.getColor("Table.background", Color.lightGray));
elementsPerRow=1;
enableEvents(AWTEvent.MOUSE_EVENT_MASK | AWTEvent.KEY_EVENT_MASK);
}
public void setSizes(int th_width, int th_height, int th_margin, int th_textHeight, int th_int_margin){
this.th_width=Math.max(10, th_width);
this.th_height=Math.max(10, th_height);
this.th_margin=Math.max(5, th_margin);
this.th_textHeight=Math.max(10, th_textHeight);
this.th_int_margin=Math.max(0, th_int_margin);
boxSize=new Dimension(th_width+2*th_margin, th_height+th_textHeight+2*th_margin);
maxThumbSize=new Dimension(th_width-2*th_int_margin, th_height-2*th_int_margin);
for(int i=0; i<elements.size(); i++){
getThumbElement(i).sizeImage();
}
invalidate();
}
public void resizeTo(int desiredWidth){
int numElements=elements.size();
int w=Math.max(boxSize.width, desiredWidth);
int bpr=w/boxSize.width;
int numRows=(numElements/bpr)+1;
int h=numRows*boxSize.height;
setPreferredSize(new Dimension(w, h));
revalidate();
repaint();
}
public ThumbElement getThumbElement(int p){
return elements.get(p);
}
public ThumbElement[] getThumbElements(){
return elements.toArray(new ThumbElement[elements.size()]);
}
public ThumbElement getElementFor(Object object){
ThumbElement result=null;
for(int i=0; i<elements.size(); i++){
if(getThumbElement(i).getUserObject()==object){
result=getThumbElement(i);
break;
}
}
return result;
}
public int getThumbElementCount(){
return elements.size();
}
public void removeThumbElementAt(int p){
if(elements!=null){
ThumbElement o=elements.get(p);
if(o!=null){
if(o==current)
current=null;
elements.remove(p);
invalidate();
}
}
}
public void removeAllThumbElements(){
elements.clear();
current=null;
invalidate();
repaint();
}
public ThumbElement addThumbElement(int p, Object userObject, ImageIcon img, String text){
ThumbElement th=new ThumbElement(userObject, img, text);
if(p>=0)
elements.add(p, th);
else
elements.add(th);
invalidate();
return th;
}
public ThumbElement addThumbElement(Object userObject, ImageIcon img, String text){
return addThumbElement(-1, userObject, img, text);
}
/** Getter for property current.
* @return Value of property current.
*
*/
public ThumbElement getCurrent() {
return current;
}
public Object getCurrentObject(){
return current==null ? null : current.getUserObject();
}
/** Setter for property current.
* @param th New value of {@link #current}.
*
*/
public void setCurrent(ThumbElement th) {
if(current!=null){
Rectangle r=getRectFor(getIndexOf(current));
if(r!=null)
repaint(r);
}
if(th!=null && elements.contains(th)){
current = th;
Rectangle r=getRectFor(getIndexOf(current));
if(r!=null)
repaint(r);
scrollRectToVisible(r);
}
else
current=null;
}
public void checkCurrentVisibility() {
if(current!=null){
Rectangle r=getRectFor(getIndexOf(current));
if(r!=null)
scrollRectToVisible(r);
}
}
public void setCurrentObject(Object o){
ThumbElement th=null;
for(int i=0; i<elements.size(); i++){
if(getThumbElement(i).getUserObject()==o){
th=getThumbElement(i);
break;
}
}
setCurrent(th);
}
/** Getter for property font.
* @return Value of property font.
*
*/
@Override
public Font getFont() {
return font;
}
/** Setter for property font.
* @param font New value of property font.
*
*/
@Override
public void setFont(Font font) {
this.font = font;
repaint();
}
public int getIndexOf(ThumbElement th){
return th==null ? -1 : elements.indexOf(th);
}
public Rectangle getRectFor(int p){
Rectangle r=null;
if(p>=0 && p<elements.size()){
r=new Rectangle(boxSize);
r.x=(p%elementsPerRow)*r.width;
r.y=(p/elementsPerRow)*r.height;
}
return r;
}
@Override
public void doLayout(){
Dimension d=getPreferredSize();
int w=Math.max(d.width, boxSize.width);
elementsPerRow=w/boxSize.width;
/*
* ELIMINATED DUE TO
* RECURSIVE CALLS IN 1.4
* TEST IN 1.3.1
*
int nRows=elements.size()/elementsPerRow+1;
int h=nRows*boxSize.height;
if(getHeight()!=h)
setBounds(getX(), getY(), w, h);
*/
}
@Override
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2=(Graphics2D)g;
Rectangle clip=g2.getClipBounds();
Rectangle r=new Rectangle(boxSize);
for(int i=0; i<elements.size(); i++){
r.x=(i%elementsPerRow)*r.width;
r.y=(i/elementsPerRow)*r.height;
if(r.intersects(clip)){
getThumbElement(i).paint(g2, r);
}
}
}
public void addActionListener(ActionListener l) {
listenersList.add(ActionListener.class, l);
}
public void removeActionListener(ActionListener l) {
listenersList.remove(ActionListener.class, l);
}
protected void fireActionPerformed() {
ActionEvent actionEvent=new ActionEvent(current, ActionEvent.ACTION_PERFORMED, "");
Object[] listeners = listenersList.getListeners(ActionListener.class);
for (Object lst : listeners) {
((ActionListener)lst).actionPerformed(actionEvent);
}
}
public void addListSelectionListener(ListSelectionListener l){
listenersList.add(ListSelectionListener.class, l);
}
public void removeListSelectionListener(ListSelectionListener l) {
listenersList.remove(ListSelectionListener.class, l);
}
protected void fireSelectionChanged(int index) {
ListSelectionEvent listEvent=new ListSelectionEvent(current, index, index, false);
Object[] listeners = listenersList.getListeners(ListSelectionListener.class);
for (Object lst : listeners) {
((ListSelectionListener)lst).valueChanged(listEvent);
}
}
@Override
protected void processMouseEvent(MouseEvent ev){
if(isEnabled() && ev.getID()==MouseEvent.MOUSE_RELEASED){
int p=(ev.getX()/boxSize.width)+elementsPerRow*(ev.getY()/boxSize.height);
if(p<elements.size()){
if(ev.getClickCount()==1){
setCurrent(getThumbElement(p));
fireSelectionChanged(p);
}
else if(ev.getClickCount()==2/* && current==getThumbElement(p)*/){
fireActionPerformed();
}
}
}
}
@Override
protected void processKeyEvent(KeyEvent e){
}
public class ThumbElement {
ImageIcon image;
String text;
Object userObject;
/** Creates a new instance of ThumbElement */
ThumbElement(Object userObject, ImageIcon image, String text) {
setUserObject(userObject);
setImage(image);
setText(text);
}
/** Getter for property userObject.
* @return Value of property userObject.
*
*/
public java.lang.Object getUserObject() {
return userObject;
}
/** Setter for property userObject.
* @param userObject New value of property userObject.
*
*/
public void setUserObject(java.lang.Object userObject) {
this.userObject = userObject;
}
/** Getter for property text.
* @return Value of property text.
*
*/
public java.lang.String getText() {
return text;
}
/** Setter for property text.
* @param text New value of property text.
*
*/
public void setText(java.lang.String text) {
this.text = text;
}
/** Getter for property image.
* @return Value of property image.
*
*/
public ImageIcon getImage() {
return image;
}
/** Setter for property image.
* @param image New value of property image.
*
*/
public void setImage(ImageIcon image) {
this.image = image;
if(image!=null)
//image.setImageObserver(ThumbsExplorerPanel.this);
sizeImage();
}
protected void sizeImage(){
if(image!=null){
int w=image.getIconWidth();
int h=image.getIconHeight();
if(w>maxThumbSize.width || h>maxThumbSize.height){
double f=Math.min((double)maxThumbSize.width/w, (double)maxThumbSize.height/h);
image=new ImageIcon(image.getImage().getScaledInstance((int)(f*w),
(int)(f*h),
Image.SCALE_SMOOTH));
}
}
}
protected void paint(Graphics2D g2, Rectangle r){
g2.setBackground(LFUtil.getColor("Table.background", Color.white));
g2.clearRect(r.x, r.y, r.width, r.height);
if(current==this){
Rectangle r2=new Rectangle(r.x+th_margin/2, r.y+th_margin/2,
r.width-th_margin, r.height-th_margin);
g2.setColor(LFUtil.getSysColor("activeCaption", Color.orange));
g2.fill(r2);
g2.setColor(LFUtil.getSysColor("activeCaptionBorder", Color.red));
g2.draw(r2);
}
if(image!=null){
image.paintIcon(ThumbsExplorerPanel.this, g2,
r.x+((th_width+2*th_margin)-image.getIconWidth())/2,
r.y+((th_height+2*th_margin)-image.getIconHeight())/2);
}
g2.setColor(LFUtil.getColor("Table.gridColor", Color.gray));
g2.setStroke(BORDER_STROKE);
g2.drawRect(r.x+th_margin, r.y+th_margin, th_width, th_height);
if(text!=null && text.length()>0){
if(current==this)
g2.setColor(LFUtil.getSysColor("activeCaptionText", Color.black));
else
g2.setColor(LFUtil.getColor("Table.foreground", Color.black));
FontRenderContext frc=g2.getFontRenderContext();
Font f2=LFUtil.getFont("Table.font", font);
TextLayout layout = new TextLayout(text, f2, frc);
Rectangle2D txRect=layout.getBounds();
if(layout.getBounds().getWidth()>th_width){
TextHitInfo hitInfo = layout.hitTestChar(th_width-20, 0);
int insPoint = hitInfo.getInsertionIndex();
if(insPoint>0){
layout=new TextLayout(text.substring(0, insPoint)+"...", f2, frc);
txRect=layout.getBounds();
}
}
layout.draw(g2,
(float)r.x+th_margin+(th_width-(float)txRect.getWidth())/2,
(float)r.y+2*th_margin+th_height);
}
}
}
}