/* Copyright (C) 2006 Christian Schneider
*
* This file is part of Nomad.
*
* Nomad 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.
*
* Nomad 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.
*
* You should have received a copy of the GNU General Public License
* along with Nomad; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.sf.nmedit.jtheme.component.plaf;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Component;
import java.awt.Composite;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Point;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIDefaults;
import javax.swing.border.Border;
import net.sf.nmedit.jtheme.component.JTButtonControl;
import net.sf.nmedit.jtheme.component.JTComponent;
import net.sf.nmedit.jtheme.component.JTControl;
import net.sf.nmedit.jtheme.component.misc.CallDescriptor;
import net.sf.nmedit.nmutils.Platform;
public class JTBasicButtonControlUI extends JTButtonControlUI implements SwingConstants
{
// protected static final String INCREASE = "increase";
// protected static final String DECREASE = "decrease";
// protected static final String DEFAULTVALUE = "default.value";
protected JTButtonControl control;
private Insets paddingInsets = new Insets(0,0,0,0); // inside border
private int iconLabelGap = 2; // space between icon and label
private Border border;
private Border selectedBorder;
private transient Insets borderInsets;
private transient Insets selectedBorderInsets;
private transient Insets maxBorderInsets;
private transient int internalHoverIndex = -1;
private transient int internalArmedIndex = -1;
private Color stateBackground ;
private Color selectedBackground ;
public JTBasicButtonControlUI(JTButtonControl control)
{
this.control = control;
}
public static JTComponentUI createUI(JComponent c)
{
return new JTBasicButtonControlUI((JTButtonControl) c);
}
private int normalizeButtonIndex(int index)
{
if (index<0) return -1;
else if (index>=range()) return -1;
else return index;
}
public void setHoveredAt(int index)
{
index = normalizeButtonIndex(index);
if (internalHoverIndex != index)
{
internalHoverIndex = index;
control.repaint();
}
}
public void setArmedAt(int index)
{
index = normalizeButtonIndex(index);
if (internalArmedIndex != index)
{
internalArmedIndex = index;
control.repaint();
}
}
private Insets getBorderInsets()
{
if (borderInsets == null)
borderInsets = border.getBorderInsets(control);
return borderInsets;
}
private Insets getSelectedBorderInsets()
{
if (selectedBorderInsets == null)
selectedBorderInsets = selectedBorder.getBorderInsets(control);
return selectedBorderInsets;
}
private Insets getMaxBorderInsets()
{
if (maxBorderInsets == null)
{
Insets a = getBorderInsets();
Insets b = getSelectedBorderInsets();
maxBorderInsets = new Insets(
Math.max(a.top, b.top),
Math.max(a.left, b.left),
Math.max(a.bottom, b.bottom),
Math.max(a.right, b.right)
);
}
return maxBorderInsets;
}
public void installUI(JComponent c)
{
super.installUI(c);
checkComponent(c);
UIDefaults defaults = control.getContext().getUIDefaults();
control.setFocusable(true);
border = defaults.getBorder(BORDER_KEY);
selectedBorder = defaults.getBorder(SELECTED_BORDER_KEY);
Color background = defaults.getColor(BACKGROUND_KEY);
if (background != null)
control.setBackground(background);
else
{
background = control.getBackground();
}
stateBackground = defaults.getColor(BACKGROUND_STATE_KEY);
selectedBackground = defaults.getColor(BACKGROUND_SELECTED_KEY);
if (selectedBackground == null)
selectedBackground = stateBackground;
if (border == null)
border = BorderFactory.createEmptyBorder();
if (selectedBorder == null)
selectedBorder = border;
}
public void uninstallUI(JComponent c)
{
checkComponent(c);
super.uninstallUI(c);
}
private void checkComponent(JComponent c)
{
if (c != this.control)
throw new IllegalArgumentException("invalid component "+c);
}
public void paintStaticLayer(Graphics2D g, JTComponent c)
{
checkComponent(c);
if (c.isOpaque())
{
g.setColor(c.getBackground());
g.fillRect(0, 0, c.getWidth(), c.getHeight());
}
}
private static final Composite overlayComposite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.7f);
public void paintDynamicLayer(Graphics2D g, JTComponent c)
{
checkComponent(c);
checkContents();
final int range = range();
if (range <= 0)
return;
int w = control.getWidth();
int h = control.getHeight();
int btnw = w;
int btnh = h;
final int min = control.getMinValue();
final int value = control.getValue();
final int intSelectionIndex = min+value;
if (control.isCyclic())
{
Icon icon = btnIcons[intSelectionIndex];
String label = btnLabels[intSelectionIndex];
int defaultSelection = -1;
if (control.isToggleEnabledRequested() && (!control.isIncrementModeEnabled()))
{
// TODO
defaultSelection = control.getDefaultValue()-min;
if (defaultSelection>control.getMaxValue()-min)
defaultSelection = control.getMaxValue()-min;
}
if (control.isExtensionAdapterSet() && control.getExtensionValue()!=0)
{
Color overlay = getExtensionColor(control.getControlAdapter().getParameter());
Composite tmpComposite = g.getComposite();
g.setComposite(overlayComposite);
g.setColor(overlay);
g.fillRect(0, 0, btnw, btnh);
g.setComposite(tmpComposite);
}
paintButton(g, defaultSelection, intSelectionIndex, icon, label, 0, 0, btnw, btnh);
}
else
{
float dx, dy;
if (control.getOrientation() == HORIZONTAL)
{
float btnf = w/(float)range;
btnw = (int)btnf;
dx = btnf;
dy = 0;
}
else
{
// vertical
float btnf = h/(float)range;
btnh = (int)btnf;
dx = 0;
dy = btnf;
}
float x = 0;
float y = 0;
// paint the overlay to represent the morph value
if (control.isExtensionAdapterSet() && control.getExtensionValue()!=0)
{
Color overlay = getExtensionColor(control.getControlAdapter().getParameter());
Composite tmpComposite = g.getComposite();
g.setComposite(overlayComposite);
g.setColor(overlay);
int extension = control.getExtensionValue();
int xRect, yRect, wRect,hRect;
if (extension > 0)
{
if (control.isIncrementModeEnabled())
{ // overlay on the + button
// TODO: check whether this inversion is needed, normally this is
// done previously, only increment mode need that.
xRect = control.getOrientation() == HORIZONTAL ? (int)dx:0;
yRect = control.getOrientation() == HORIZONTAL ? (int)dy:0;
wRect = btnw;
hRect = btnh;
}
else
{
xRect = (int)(dx*(control.getValue()+1));
yRect = (int)(dy*(control.getValue()+1));
wRect = (int)(dx*extension);
hRect = (int)(dy*extension);
}
}
else
{
if (control.isIncrementModeEnabled()) {
xRect = control.getOrientation() == HORIZONTAL ? 0:(int)dx;
yRect = control.getOrientation() == HORIZONTAL ? 0:(int)dy;
wRect = btnw;
hRect = btnh;
}
else
{
xRect = (int)(dx*control.getValue()+dx*extension);
yRect = (int)(dy*control.getValue()+dy*extension);
wRect = (int)(-dx*extension);
hRect = (int)(-dy*extension);
}
}
wRect = wRect == 0 ? btnw : wRect;
hRect = hRect == 0 ? btnh : hRect;
g.fillRect(xRect,yRect,wRect,hRect);
g.setComposite(tmpComposite);
}
for (int i=0;i<range;i++)
{
int idx = i;
if (control.isReversed()) {
idx = range - i -1 ;
}
Icon icon = btnIcons[idx];
String label = btnLabels[idx];
paintButton(g, intSelectionIndex, min+idx, icon, label, (int)x, (int)y, btnw, btnh);
x+=dx;
y+=dy;
}
}
/*
g.setColor(Color.red);
g.drawRect(0, 0, c.getWidth()-1, c.getHeight()-1);
*/
}
public int getInternalButtonIndexForLocation(Point loc)
{
return getInternalButtonIndexForLocation(loc.x, loc.y);
}
public int getInternalButtonIndexForLocation(int x, int y)
{
checkContents();
int index = -1;
if (!control.isCyclic())
{
int range = range();
if (range>0)
{
int size;
int pos;
if (control.getOrientation()==HORIZONTAL)
{
pos = x;
size = control.getWidth();
}
else
{
pos = y;
size = control.getHeight();
}
if (size>0)
{
if (range==0)
index = -1;
else
{
int d = size/range;
if (d==0)
index = -1;
else
{
index = pos/d;
if (index<0 || index>range())
index = -1;
}
}
}
}
}
else
{
index = control.getValue()-control.getMinValue();
}
if (control.isReversed()) {
return range() - index - 1;
} else {
return index;
}
}
private void paintButton(Graphics2D g, int intBtnSelIndex,
int intBtnIndex, Icon icon, String label,
int x, int y, int btnw, int btnh)
{
// intButtonIndex in [0..range())
boolean selected;
Border border;
Insets borderInsets;
boolean armed = internalArmedIndex == intBtnIndex;
boolean hovered = internalHoverIndex == intBtnIndex;
if ( (!control.isIncrementModeEnabled() && !control.isCyclic() && intBtnSelIndex == intBtnIndex)
|| (intBtnSelIndex != intBtnIndex && control.isCyclic())
|| armed)
{
selected = true;
border = selectedBorder;
borderInsets = getSelectedBorderInsets();
}
else
{
selected = false;
border = this.border;
borderInsets = getBorderInsets();
}
Color bg = null;
if ((control.hasFocus() && (selected || control.isCyclic()))||hovered||armed)
bg = stateBackground;
else if (selected)
bg = selectedBackground;
if (bg != null)
{
g.setColor(bg);
g.fillRect(x+borderInsets.left, y+borderInsets.top,
btnw-(borderInsets.left+borderInsets.right),
btnh-(borderInsets.top+borderInsets.bottom));
}
border.paintBorder(control, g, x, y, btnw, btnh);
int px = x+borderInsets.left+paddingInsets.left;
int py = y+borderInsets.top+paddingInsets.top;
if (selected) px+=1;
int mid = y+btnh/2;
int contentwidth = 0;
FontMetrics fm = null;
if (icon != null)
contentwidth+=icon.getIconWidth();
if (label != null)
{
fm = getFontMetrics();
contentwidth+=iconLabelGap+fm.stringWidth(label);
}
// inner width
int t = btnw-(borderInsets.left+paddingInsets.left+borderInsets.right+paddingInsets.right);
int off = (t-contentwidth)/2;
px += off;
if (icon != null)
{
int top = Math.max(py, (2*mid-icon.getIconHeight())/2);
if (selected) top+=1;
icon.paintIcon(control, g, px, top);
px+=icon.getIconWidth()+iconLabelGap;
}
if (label!=null)
{
g.setColor(control.getForeground());
int top = Math.max(py, (2*mid+(fm.getAscent()+fm.getDescent()))/2-fm.getDescent());
if (selected) top+=1;
g.drawString(label, px, top);
}
}
private int range()
{
if (control.isIncrementModeEnabled())
return 2;
else
return control.getMaxValue()-control.getMinValue()+1;
}
private transient String[] btnLabels;
private transient Icon[] btnIcons;
private transient Dimension preferredButtonSize;
private boolean eq(Object a, Object b)
{
return a==b || (a!=null && a.equals(b));
}
private boolean checkLabels()
{
int range = range();
boolean valid = true;
if (btnLabels == null || btnLabels.length != range)
{
// rebuild strings
btnLabels = new String[range];
int min = control.getMinValue();
for (int i=0;i<range;i++)
btnLabels[i] = control.getText(min+i);
valid = false; // not valid
}
else
{
int min = control.getMinValue();
String label;
for (int i=0;i<range;i++)
{
label = control.getText(min+i);
if (!eq(btnLabels[i], label))
{
btnLabels[i] = label;
valid = false;
}
}
}
// returns true is valid, false if labels changed
return valid;
}
private boolean checkIcons()
{
int range = range();
boolean valid = true;
if (btnIcons == null || btnIcons.length != range)
{
// rebuild strings
btnIcons = new Icon[range];
int min = control.getMinValue();
for (int i=0;i<range;i++)
btnIcons[i] = control.getIcon(min+i);
valid = false; // not valid
}
else
{
int min = control.getMinValue();
Icon icon;
for (int i=0;i<range;i++)
{
icon = control.getIcon(min+i);
if (btnIcons[i]!=icon)
{
btnIcons[i] = icon;
valid = false;
}
}
}
// returns true is valid, false if icons changed
return valid;
}
private boolean checkContents()
{
boolean result = false;
// both check*() methods must be called
result |= checkIcons();
result |= checkLabels();
return result;
}
private boolean checkPreferredContents()
{
if (checkContents() || preferredButtonSize == null)
{
computePreferedButtonSize();
return true;
}
return false;
}
public Dimension getPreferredSize(JComponent c)
{
checkComponent(c);
checkPreferredContents();
Dimension d = new Dimension(preferredButtonSize);
if (!control.isCyclic())
{
if (control.getOrientation() == HORIZONTAL)
{
d.width *= range();
}
else
{
d.height*= range();
}
}
return d;
}
private transient FontMetrics fontMetrics;
private FontMetrics getFontMetrics()
{
if (fontMetrics == null)
fontMetrics = control.getFontMetrics(control.getFont());
return fontMetrics;
}
//private static final String CONTROL_LISTENER_KEY = JTBasicButtonControlUI.class.getName()
// +".CONTROL_LISTENER";
private transient BasicButtonListener bblInstance;
protected BasicButtonListener createControlListener(JTControl control)
{
if (bblInstance == null)
{
bblInstance = new BasicButtonListener();
}
return bblInstance;
}
protected BasicButtonListener getBasicButtonListener(JTButtonControl btn)
{
for (MouseListener ml : btn.getMouseListeners())
{
if (ml instanceof BasicButtonListener)
{
return (BasicButtonListener) ml;
}
}
return null;
}
protected static class BasicButtonListener extends BasicControlListener
{
protected JTButtonControl getControl(ComponentEvent e)
{
Component c = e.getComponent();
if (c!= null && c instanceof JTButtonControl)
return (JTButtonControl) c;
return null;
}
protected JTBasicButtonControlUI getUI(JTButtonControl control)
{
Object ui = control.getUI();
if (ui != null && ui instanceof JTBasicButtonControlUI)
return (JTBasicButtonControlUI) ui;
else
return null;
}
transient JTButtonControl selectedControl;
transient JTBasicButtonControlUI selectedUI;
transient int internalSelectedButtonIndex;
public boolean select(MouseEvent e)
{
JTButtonControl c = getControl(e);
JTBasicButtonControlUI ui = null;
int index = -1;
if (c!=null)
{
ui = getUI(c);
if (ui != null)
index = ui.getInternalButtonIndexForLocation(e.getX(), e.getY());
}
selectedControl = c;
selectedUI = ui;
internalSelectedButtonIndex = index;
return index>=0;
}
protected void checkArmedHoveredState(MouseEvent e)
{
// ensure that armed/hovered state does not change while
// dragging over button
if(SwingUtilities.isLeftMouseButton(e)
|| SwingUtilities.isMiddleMouseButton(e)
|| SwingUtilities.isRightMouseButton(e))
return ;
if (select(e))
{
if (selectedControl.contains(e.getX(), e.getY()))
{
if (Platform.isLeftMouseButtonOnly(e))
{
selectedUI.setHoveredAt(-1);
selectedUI.setArmedAt(internalSelectedButtonIndex);
}
else
{
selectedUI.setArmedAt(-1);
selectedUI.setHoveredAt(internalSelectedButtonIndex);
}
}
else
{
selectedUI.setHoveredAt(-1);
selectedUI.setArmedAt(-1);
}
}
}
public void mouseEntered(MouseEvent e)
{
checkArmedHoveredState(e);
}
public void mouseExited(MouseEvent e)
{
checkArmedHoveredState(e);
}
boolean wasPopupTrigger = false;
public boolean handlePopup(MouseEvent e)
{
if (Platform.isPopupTrigger(e))
{
wasPopupTrigger = true;
JTButtonControl control = getControl(e);
control.showControlPopup(e);
return true;
}
return false;
}
public void mousePressed(MouseEvent e)
{
if (handlePopup(e))
{
// popup is shown
}
else
{
checkArmedHoveredState(e);
if (!e.getComponent().hasFocus())
e.getComponent().requestFocus();
wasPopupTrigger = false;
}
}
public void mouseReleased(MouseEvent e)
{
if (handlePopup(e))
{
// popup is shown
}
else if (select(e) && wasPopupTrigger == false)
{
if (Platform.isLeftMouseButtonOnly(e) )//&& e.getClickCount() == 1)
{
CallDescriptor call = selectedControl.getCall();
if ( call != null)
{
call.call();
}
else
{
if (selectedControl.isExtensionAdapterSet() && isExtensionSelected(e))
{
// TODO compute extension value
int newValue;
if (selectedControl.isIncrementModeEnabled())
{
if (selectedControl.getOrientation() == SwingConstants.VERTICAL) {
newValue = selectedControl.getExtensionValue()+(internalSelectedButtonIndex>0?-1:+1);
} else {
newValue = selectedControl.getExtensionValue()+(internalSelectedButtonIndex>0?+1:-1);
}
}
else
if (selectedControl.isCyclic())
{
newValue = selectedControl.getExtensionValue()+1;
if (newValue>selectedControl.getExtMaxValue())
newValue = selectedControl.getExtMinValue();
}
else
{
newValue = internalSelectedButtonIndex - selectedControl.getValue();
}
selectedControl.setExtensionValue(newValue);
}
else
{
int newValue;
if (selectedControl.isIncrementModeEnabled())
{
if (selectedControl.getOrientation() == SwingConstants.VERTICAL) {
newValue = selectedControl.getValue()+(internalSelectedButtonIndex>0?-1:+1);
} else {
newValue = selectedControl.getValue()+(internalSelectedButtonIndex>0?+1:-1);
}
}
else
if (selectedControl.isCyclic())
{
newValue = selectedControl.getValue()+1;
if (newValue>selectedControl.getMaxValue())
newValue = selectedControl.getMinValue();
}
else
{
newValue = selectedControl.getMinValue()+internalSelectedButtonIndex;
}
selectedControl.setValue(newValue);
}
}
}
selectedUI.setHoveredAt(-1);
selectedUI.setArmedAt(-1);
}
}
public void mouseDragged(MouseEvent e)
{
checkArmedHoveredState(e);
}
public void mouseMoved(MouseEvent e)
{
checkArmedHoveredState(e);
}
}
private void computePreferedButtonSize()
{
int maxIconWidth = 0;
int maxIconHeight = 0;
int maxStringWidth = 0;
FontMetrics fm = getFontMetrics();
for (int i=0;i<btnLabels.length;i++)
{
// btnLabels.length == btnIcons.length
Icon icon = btnIcons[i];
if (icon != null)
{
maxIconWidth = Math.max(maxIconWidth, icon.getIconWidth());
maxIconHeight = Math.max(maxIconHeight, icon.getIconHeight());
}
String label = btnLabels[i];
if (label != null)
{
int stringWidth = SwingUtilities.computeStringWidth(fm, label);
maxStringWidth = Math.max(maxStringWidth, stringWidth);
}
}
if (preferredButtonSize == null)
preferredButtonSize = new Dimension();
Insets mb = getMaxBorderInsets();
int pw = mb.left + paddingInsets.left
+ (maxIconWidth>0?(maxIconWidth+iconLabelGap):0) + maxStringWidth
+ paddingInsets.right +mb.right;
int ph = mb.top + paddingInsets.top
+ Math.max(fm.getAscent()+fm.getDescent(), maxIconHeight)
+ paddingInsets.bottom +mb.bottom;
preferredButtonSize.setSize(pw, ph);
}
}