/*
This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2010 Servoy BV
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License as published by the Free
Software Foundation; either version 3 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along
with this program; if not, see http://www.gnu.org/licenses or write to the Free
Software Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
*/
package com.servoy.j2db.util.toolbar;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.Point;
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import java.util.EventObject;
import java.util.Vector;
import javax.swing.JPanel;
import javax.swing.JToolBar;
import javax.swing.border.EmptyBorder;
import javax.swing.event.MouseInputListener;
import com.servoy.j2db.util.UIUtils;
/**
* Toolbar provides a component which is useful for displaying commonly used actions. It can be dragged inside its <code>ToolbarPanel</code> to customize its
* location.
*/
public class Toolbar extends JToolBar
/*
* nbif compat implements MouseInputListener /*nbend
*/
{
/** Constant of grip layout constraint. */
private static final String GRIP = "Grip"; // NOI18N //$NON-NLS-1$
/** Constant of action layout constraint. */
private static final String ACTION = "Action"; // NOI18N //$NON-NLS-1$
/** Basic toolbar height. */
public static/*
* nbif compat nbelse
*/
final/* nbend */
int BASIC_HEIGHT = 26;
/**
* 5 pixels is tolerance of toolbar height so toolbar can be high (BASIC_HEIGHT + HEIGHT_TOLERANCE) but it will be set to BASIC_HEIGHT high.
*/
static int HEIGHT_TOLERANCE = 5;
/** TOP of toolbar empty border. */
static int TOP = 0;
/** LEFT of toolbar empty border. */
static int LEFT = 3;
/** BOTTOM of toolbar empty border. */
static int BOTTOM = 0;
/** RIGHT of toolbar empty border. */
static int RIGHT = 3;
/** is toolbar floatable */
private boolean floatable;
/** Toolbar DnDListener */
private DnDListener listener;
/** Toolbar mouse listener */
private ToolbarMouseListener mouseListener;
/** display name of the toolbar */
private String displayName;
/**
* Create a new not floatable Toolbar with specified programmatic name and display name
*/
public Toolbar(String name, String displayName)
{
this(name, displayName, false);
}
/**
* Create a new <code>Toolbar</code>.
*
* @param name a <code>String</code> containing the associated name
* @param f specified if Toolbar is floatable
*/
public Toolbar(String name, String displayName, boolean f)
{
super();
setDisplayName(displayName);
initAll(name, f);
setFocusable(false);
}
private void initAll(String name, boolean f)
{
floatable = f;
mouseListener = null;
setName(name);
// setFloatable (true);
// Border border = getBorder();
super.setFloatable(false); //never makes it really floatable
// setBorder (new CompoundBorder (new EtchedBorder(),
// getBorder()));//new EmptyBorder (TOP, LEFT, BOTTOM, RIGHT)));
setLayout(new InnerLayout(0, 0));
putClientProperty("JToolBar.isRollover", new Boolean(true)); // NOI18N //$NON-NLS-1$
if (f) addGrip();
}
@Override
public void setEnabled(boolean b)
{
Component[] all = getComponents();
for (Component element : all)
{
Component c = element;
c.setEnabled(b);
}
super.setEnabled(b);
}
private boolean actAsFloatable = false;
@Override
public void paint(Graphics g)
{
actAsFloatable = floatable;
try
{
super.paint(g);
}
finally
{
actAsFloatable = false;
}
}
@Override
public boolean isFloatable()
{
return actAsFloatable;
}
/** Add new Component as ACTION. */
@Override
public Component add(Component comp)
{
add(ACTION, comp);
return comp;
}
/** Removes all ACTION components. */
@Override
public void removeAll()
{
super.removeAll();
addGrip();
}
private ToolbarBump dragarea;
/**
* When Toolbar is floatable, ToolbarGrip is added as Grip as first toolbar component
*/
void addGrip()
{
if (floatable)
{
dragarea = new ToolbarBump();
if (mouseListener == null) mouseListener = new ToolbarMouseListener();
dragarea.addMouseListener(mouseListener);
dragarea.addMouseMotionListener(mouseListener);
add(GRIP, dragarea);
// addSeparator (new Dimension (4, 1));
}
}
@Override
public void setFloatable(boolean b)
{
if (b)
{
addGrip();
}
else if (dragarea != null)
{
remove(dragarea);
}
super.setFloatable(b);
}
/**
* Compute with HEIGHT_TOLERANCE number of rows for specific toolbar height.
*
* @param height of some toolbar
* @return number of rows
*/
static int rowCount(int height)
{
int rows = 1;
int max_height = (BASIC_HEIGHT + HEIGHT_TOLERANCE);
while (height > max_height)
{
rows++;
height -= max_height;
}
return rows;
}
/**
* Set DnDListener to Toolbar.
*
* @param DndListener for toolbar
*/
void setDnDListener(DnDListener l)
{
listener = l;
}
/**
* @return Display name of this toolbar. Display name is localizable, on the contrary to the programmatic name
*/
public String getDisplayName()
{
return displayName;
}
/**
* Sets new display name of this toolbar. Display name is localizable, on the contrary to the programmatic name
*/
private void setDisplayName(String displayName)
{
//System.out.println("setting display name...");
this.displayName = displayName;
}
/**
* Fire drag of Toolbar
*
* @param dx distance of horizontal dragging
* @param dy distance of vertical dragging
* @param type type of toolbar dragging
*/
protected void fireDragToolbar(int dx, int dy, int type)
{
if (listener != null) listener.dragToolbar(new DnDEvent(this, getName(), dx, dy, type));
}
/**
* Fire drop of Toolbar
*
* @param dx distance of horizontal dropping
* @param dy distance of vertical dropping
* @param type type of toolbar dropping
*/
protected void fireDropToolbar(int dx, int dy, int type)
{
if (listener != null) listener.dropToolbar(new DnDEvent(this, getName(), dx, dy, type));
}
/** Toolbar mouse listener. */
class ToolbarMouseListener implements MouseInputListener
{
/** Is toolbar dragging now. */
private boolean dragging;
/** Start point of dragging. */
private Point startPoint;
public ToolbarMouseListener()
{
dragging = false;
startPoint = null;
}
/** Invoked when the mouse has been clicked on a component. */
public void mouseClicked(MouseEvent e)
{
}
/** Invoked when the mouse enters a component. */
public void mouseEntered(MouseEvent e)
{
}
/** Invoked when the mouse exits a component. */
public void mouseExited(MouseEvent e)
{
}
/** Invoked when a mouse button has been pressed on a component. */
public void mousePressed(MouseEvent e)
{
startPoint = new Point(e.getX(), e.getY());
}
/** Invoked when a mouse button has been released on a component. */
public void mouseReleased(MouseEvent e)
{
if (dragging)
{
fireDropToolbar(e.getX() - startPoint.x, e.getY() - startPoint.y, DnDEvent.DND_ONE);
dragging = false;
}
}
/** Invoked when a mouse button is pressed on a component and then dragged. */
public void mouseDragged(MouseEvent e)
{
int m = e.getModifiers();
int type = DnDEvent.DND_ONE;
if (UIUtils.isCommandKeyDown(e)) type = DnDEvent.DND_LINE;
else if (((m & InputEvent.BUTTON2_MASK) != 0) || ((m & InputEvent.BUTTON3_MASK) != 0)) type = DnDEvent.DND_END;
if (startPoint == null) startPoint = new Point(e.getX(), e.getY());
fireDragToolbar(e.getX() - startPoint.x, e.getY() - startPoint.y, type);
dragging = true;
}
/**
* Invoked when the mouse button has been moved on a component (with no buttons no down).
*/
public void mouseMoved(MouseEvent e)
{
}
} // end of inner class ToolbarMouseListener
/*
* nbif compat public void mouseClicked (MouseEvent e) { if (mouseListener == null) mouseListener = new ToolbarMouseListener (); mouseListener.mouseClicked
* (e); } public void mouseEntered (MouseEvent e) { if (mouseListener == null) mouseListener = new ToolbarMouseListener (); mouseListener.mouseEntered (e);
* } public void mouseExited (MouseEvent e) { if (mouseListener == null) mouseListener = new ToolbarMouseListener (); mouseListener.mouseExited (e); }
* public void mousePressed (MouseEvent e) { if (mouseListener == null) mouseListener = new ToolbarMouseListener (); mouseListener.mousePressed (e); }
* public void mouseReleased (MouseEvent e) { if (mouseListener == null) mouseListener = new ToolbarMouseListener (); mouseListener.mouseReleased (e); }
* public void mouseDragged (MouseEvent e) { if (mouseListener == null) mouseListener = new ToolbarMouseListener (); mouseListener.mouseDragged (e); }
* public void mouseMoved (MouseEvent e) { if (mouseListener == null) mouseListener = new ToolbarMouseListener (); mouseListener.mouseMoved (e); } /*nbend
*/
/** Grip for floatable toolbar */
private class ToolbarGrip extends JPanel
{
/** Horizontal gaps. */
static final int HGAP = 1;
/** Vertical gaps. */
static final int VGAP = 1;
/** Step between two grip elements. */
static final int STEP = 1;
/** Width of grip element. */
static final int WIDTH = 2;
/** Number of grip elements. */
int columns;
/** Minimum size. */
Dimension dim;
/** Create new ToolbarGrip for default number of grip elements. */
public ToolbarGrip()
{
this(2);
}
/**
* Create new ToolbarGrip for specific number of grip elements.
*
* @param col number of grip elements
*/
public ToolbarGrip(int col)
{
super();
columns = col;
int width = (col - 1) * STEP + col * WIDTH + 2 * HGAP;
dim = new Dimension(width, width);
this.setBorder(new EmptyBorder(VGAP, HGAP, VGAP, HGAP));
this.setToolTipText(Toolbar.this.getDisplayName());
}
/** Paint grip to specific Graphics. */
@Override
public void paint(Graphics g)
{
// Dimension size = this.getSize();
// int top = VGAP;
// int bottom = size.height - 1 - VGAP;
// int height = bottom - top;
// g.setColor ( this.getBackground() );
//
// for (int i = 0, x = HGAP; i < columns; i++, x += WIDTH + STEP) {
// g.draw3DRect (x, top, WIDTH, height, true); // grip element is 3D rectangle now
// }
}
/** @return minimum size */
@Override
public Dimension getMinimumSize()
{
return dim;
}
/** @return preferred size */
@Override
public Dimension getPreferredSize()
{
return this.getMinimumSize();
}
} // end of inner class ToolbarGrip
/** Bumps for floatable toolbar */
private class ToolbarBump extends JPanel
{
/** Top gap. */
static final int TOPGAP = 2;
/** Bottom gap. */
static final int BOTGAP = 0;
/** Width of bump element. */
static final int WIDTH = 12; //was6
/** Minimum size. */
Dimension dim;
/** Create new ToolbarBump. */
public ToolbarBump()
{
super();
int width = WIDTH;
dim = new Dimension(width, width);
this.setToolTipText(Toolbar.this.getDisplayName());
}
/** Paint bumps to specific Graphics. */
@Override
public void paint(Graphics g)
{
// Dimension size = this.getSize ();
// int height = size.height - BOTGAP;
// g.setColor (this.getBackground ());
//
// for (int x = 0; x+1 < size.width; x+=4) {
// for (int y = TOPGAP; y+1 < height; y+=4) {
// g.setColor (this.getBackground ().brighter ());
// g.drawLine (x, y, x, y);
// if (x+5 < size.width && y+5 < height)
// g.drawLine (x+2, y+2, x+2, y+2);
// g.setColor (this.getBackground ().darker ().darker ());
// g.drawLine (x+1, y+1, x+1, y+1);
// if (x+5 < size.width && y+5 < height)
// g.drawLine (x+3, y+3, x+3, y+3);
// }
// }
}
/** @return minimum size */
@Override
public Dimension getMinimumSize()
{
return dim;
}
/** @return preferred size */
@Override
public Dimension getPreferredSize()
{
return this.getMinimumSize();
}
} // end of inner class ToolbarBump
/** Toolbar layout manager */
private class InnerLayout implements LayoutManager
{
/** Grip component */
private Component grip;
/** Vector of Components */
private final Vector actions;
/** horizontal gap */
private final int hgap;
/** vertical gap */
private final int vgap;
/**
* Constructs a new InnerLayout.
*/
public InnerLayout()
{
this(5, 5);
}
/**
* Constructs a new InnerLayout with the specified gap values.
*
* @param hgap the horizontal gap variable
* @param vgap the vertical gap variable
*/
public InnerLayout(int hgap, int vgap)
{
this.hgap = hgap;
this.vgap = vgap;
actions = new Vector();
}
/**
* Adds the specified component with the specified name to the layout.
*/
public void addLayoutComponent(String name, Component comp)
{
synchronized (comp.getTreeLock())
{
if (GRIP.equals(name))
{
grip = comp;
}
else if (ACTION.equals(name))
{
actions.addElement(comp);
}
else throw new IllegalArgumentException("cannot add to layout: unknown constraint: " + name); // NOI18N //$NON-NLS-1$
}
}
/**
* Adds the specified component to the layout, using the specified constraint object.
*/
public void addLayoutComponent(Component comp, Object constraints)
{
throw new IllegalArgumentException();
}
/**
* Removes the specified component from the layout.
*/
public void removeLayoutComponent(Component comp)
{
synchronized (comp.getTreeLock())
{
if (grip == comp) grip = null;
else actions.removeElement(comp);
}
}
/**
* Calculates the preferred size dimensions for the specified panal given the components in the specified parent container.
*/
public Dimension preferredLayoutSize(Container target)
{
synchronized (target.getTreeLock())
{
Dimension dim = new Dimension(0, 0);
int size = actions.size();
if ((grip != null) && grip.isVisible())
{
Dimension d = grip.getPreferredSize();
dim.width += d.width;
dim.width += hgap;
}
for (int i = 0; i < size; i++)
{
Component comp = (Component)actions.elementAt(i);
if (comp.isVisible())
{
Dimension d = comp.getPreferredSize();
dim.width += d.width;
dim.height = Math.max(dim.height, d.height);
dim.width += hgap;
}
}
Insets insets = target.getInsets();
dim.width += insets.left + insets.right;
dim.height += insets.top + insets.bottom;
return dim;
}
}
/**
* Calculates the minimum size dimensions for the specified panal given the components in the specified parent container.
*/
public Dimension minimumLayoutSize(Container target)
{
return preferredLayoutSize(target);
}
/**
* Calculates the maximum size dimensions for the specified panal given the components in the specified parent container.
*/
public Dimension maximumLayoutSize(Container target)
{
synchronized (target.getTreeLock())
{
Dimension dim = new Dimension(0, 0);
int size = actions.size();
if ((grip != null) && grip.isVisible())
{
Dimension d = grip.getPreferredSize();
dim.width += d.width;
dim.width += hgap;
}
Component last = null;
for (int i = 0; i < size; i++)
{
Component comp = (Component)actions.elementAt(i);
if (comp.isVisible())
{
Dimension d = comp.getPreferredSize();
dim.width += d.width;
dim.height = Math.max(dim.height, d.height);
dim.width += hgap;
last = comp;
}
}
if (last != null)
{
Dimension prefSize = last.getPreferredSize();
Dimension maxSize = last.getMaximumSize();
if (prefSize != maxSize)
{
dim.width = dim.width - prefSize.width + maxSize.width;
dim.height = Math.max(dim.height, maxSize.height);
}
}
Insets insets = target.getInsets();
dim.width += insets.left + insets.right;
dim.height += insets.top + insets.bottom;
return dim;
}
}
/**
* Lays out the container in the specified panel.
*/
public void layoutContainer(Container target)
{
synchronized (target.getTreeLock())
{
Insets insets = target.getInsets();
boolean ltr = target.getComponentOrientation().isLeftToRight();
if (!ltr)
{
insets.right += insets.left;
insets.left = insets.right - insets.left;
insets.right -= insets.left;
}
Dimension dim = target.getSize();
int fullHeight = dim.height;
int bottom = dim.height - insets.bottom - 1;
int top = 0 + insets.top;
int left = insets.left;
int maxPosition = dim.width - (insets.left + insets.right) - hgap;
int right = dim.width - insets.right;
maxPosition = right;
int height = bottom - top;
int size = actions.size();
int w, h;
if ((grip != null) && grip.isVisible())
{
Dimension d = grip.getPreferredSize();
if (ltr)
{
grip.setBounds(left, top + vgap, d.width, bottom - top - 2 * vgap);
}
else
{
grip.setBounds(maxPosition - left - d.width, top + vgap, d.width, bottom - top - 2 * vgap);
}
grip.setBounds(left, top + vgap, d.width, bottom - top - 2 * vgap);
left += d.width;
left += hgap;
}
int iMaxHeightComp = 0;
// First find the larges height
for (int i = 0; i < size; i++)
{
Component comp = (Component)actions.elementAt(i);
Dimension d = comp.getPreferredSize();
iMaxHeightComp = Math.max(iMaxHeightComp, d.height);
}
int iY = fullHeight - (fullHeight - iMaxHeightComp) / 2 - 1;
for (int i = 0; i < size; i++)
{
left += hgap;
Component comp = (Component)actions.elementAt(i);
Dimension d = comp.getPreferredSize();
Dimension minSize = comp.getMinimumSize();
w = d.width;
h = Math.min(height, d.height);
if ((left < maxPosition) && (left + w > maxPosition))
{
if (maxPosition - left >= minSize.width) w = maxPosition - left;
else w = minSize.width;
}
if (target.getComponentOrientation().isLeftToRight())
{
comp.setBounds(left, iY - h, w, h);
}
else
{
comp.setBounds(maxPosition - left - w, iY - h, w, h);
}
left += d.width;
}
}
}
/**
* Returns the alignment along the x axis.
*/
public float getLayoutAlignmentX(Container target)
{
return 0;
}
/**
* Returns the alignment along the y axis.
*/
public float getLayoutAlignmentY(Container target)
{
return (float)0.5;
}
/**
* Invalidates the layout, indicating that if the layout manager has cached information it should ne discarded.
*/
public void invalidateLayout(Container target)
{
// check target components with local vars (grip, actions)
}
} // end of class InnerLayout
/** DnDListener is Drag and Drop listener for Toolbar motion events. */
interface DnDListener extends java.util.EventListener
{
/** Invoced when toolbar is dragged. */
public void dragToolbar(DnDEvent e);
/** Invoced when toolbar is dropped. */
public void dropToolbar(DnDEvent e);
} // end of interface DnDListener
public static class ToolbarKey implements Comparable
{
private final int row;
private final int rowIndex;
private final boolean visible;
private final Toolbar toolbar;
public ToolbarKey(int row, int rowIndex, boolean visible, Toolbar toolbar)
{
this.row = row;
this.rowIndex = rowIndex;
this.visible = visible;
this.toolbar = toolbar;
}
/*
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
public int compareTo(Object o)
{
ToolbarKey key = (ToolbarKey)o;
int compare = row - key.row;
if (compare == 0)
{
compare = rowIndex - key.rowIndex;
}
return compare;
}
/**
* @return Returns the row.
*/
public int getRow()
{
return row;
}
/**
* @return Returns the rowIndex.
*/
public int getRowIndex()
{
return rowIndex;
}
/**
* @return Returns the visible.
*/
public boolean isVisible()
{
return visible;
}
public Toolbar getToolbar()
{
return toolbar;
}
}
/** DnDEvent is Toolbar's drag and drop event. */
static class DnDEvent extends EventObject
{
/** Type of DnDEvent. Dragging with only one Toolbar. */
public static final int DND_ONE = 1;
/** Type of DnDEvent. Only horizontal dragging with Toolbar and it's followers. */
public static final int DND_END = 2;
/** Type of DnDEvent. Only vertical dragging with whole lines. */
public static final int DND_LINE = 3;
/** Name of toolbar where event occured. */
/*
* nbif compat public nbelse
*/
private final/* nbend */
String name;
/** distance of horizontal dragging */
/*
* nbif compat public nbelse
*/
private final/* nbend */
int dx;
/** distance of vertical dragging */
/*
* nbif compat public nbelse
*/
private final/* nbend */
int dy;
/** Type of event. */
/*
* nbif compat public nbelse
*/
private final/* nbend */
int type;
public DnDEvent(Toolbar toolbar, String name, int dx, int dy, int type)
{
super(toolbar);
this.name = name;
this.dx = dx;
this.dy = dy;
this.type = type;
}
/** @return name of toolbar where event occured. */
public String getName()
{
return name;
}
/** @return distance of horizontal dragging */
public int getDX()
{
return dx;
}
/** @return distance of vertical dragging */
public int getDY()
{
return dy;
}
/** @return type of event. */
public int getType()
{
return type;
}
} // end of class DnDEvent
} // end of class Toolbar