/*******************************************************************************
* Copyright (c) 2010, Lukasz Milewski and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Lukasz Milewski <lukasz.milewski@gmail.com> - Initial API and implementation
*******************************************************************************/
package org.eclipse.nebula.widgets.nebulatoolbar;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.MenuAdapter;
import org.eclipse.swt.events.MenuEvent;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.MouseTrackAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Device;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Pattern;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
/**
* Toolbar widget. Imitates Windows Vista/7 toolbar found in explorer
* application.
*
* @author Lukasz Milewski <lukasz.milewski@gmail.com>
* @since 04 June 2009
*/
public class NebulaToolbar extends Canvas
{
public static final int CHEVRON = 1;
private static final int INDENT = 3;
public static final int MODE_SEVEN = 1;
public static final int MODE_VISTA = 0;
private static String CHEVRON_CHARACTERS = String.valueOf((char) 0x27EB + "" + (char) 0x27EB);
private boolean active = false;
private boolean buttonPushed = false;
private boolean chevronAdded = false;
private int chevronIndex = -1;
private int chevronPosition = 0;
private List<Integer> hiddenItemsList = new LinkedList<Integer>();
private ToolbarItem items[] = new ToolbarItem[]{};
private int itemUnderCursor = -1;
private int mode = MODE_VISTA;
private int rightAlignedIndex = -1;
private int selectedItemIndex = -1;
/**
* Parameterized constructor.
*
* @param parent Parent widget
* @param style Style
*/
public NebulaToolbar(Composite parent, int style)
{
super(parent, style | SWT.NO_BACKGROUND | SWT.DOUBLE_BUFFERED);
addInternalFocusListener();
addInternalKeyListener();
addInternalMouseListener();
addInternalPaintListener();
}
/**
* Adds chevron item.
*/
private void addChevron()
{
chevronAdded = true;
chevronIndex = items.length;
ToolbarItem item = new ToolbarItem(this, CHEVRON);
item.setText(CHEVRON_CHARACTERS);
item.setSelectionListener(new ChevronAction());
}
/**
* Adds internal focus listener.
*/
private void addInternalFocusListener()
{
addFocusListener(new FocusListener()
{
public void focusGained(FocusEvent e)
{
active = true;
if (selectedItemIndex >= 0)
{
items[selectedItemIndex].setSelected(active);
}
redraw();
}
public void focusLost(FocusEvent e)
{
active = false;
if (selectedItemIndex >= 0)
{
items[selectedItemIndex].setSelected(active);
}
redraw();
}
});
}
/**
* Adds internal key listener.
*/
private void addInternalKeyListener()
{
addKeyListener(new KeyListener()
{
public void keyPressed(KeyEvent e)
{
handleKeyPressed(e);
}
public void keyReleased(KeyEvent e)
{
handleKeyReleased(e);
}
});
}
/**
* Adds internal mouse listeners.
*/
private void addInternalMouseListener()
{
addMouseListener(new MouseAdapter()
{
@Override
public void mouseDown(MouseEvent e)
{
handleMouseDown(e);
}
@Override
public void mouseUp(MouseEvent e)
{
handleMouseUp(e);
}
});
addMouseTrackListener(new MouseTrackAdapter()
{
@Override
public void mouseExit(MouseEvent e)
{
handleMouseHover(e);
}
});
addMouseMoveListener(new MouseMoveListener()
{
public void mouseMove(MouseEvent e)
{
handleMouseHover(e);
}
});
}
/**
* Add internal paint listener.
*/
private void addInternalPaintListener()
{
addListener(SWT.Paint, new Listener()
{
public void handleEvent(Event e)
{
paint(e.gc);
}
});
}
/**
* Add item to toolbar as last.
*
* @param item ToolbarItem
*/
public void addItem(ToolbarItem item)
{
addItem(item, getItemCount());
}
/**
* Add item to toolbar on specific position.
*
* @param item ToolbarItem
* @param position Position
*/
public void addItem(ToolbarItem item, int position)
{
checkWidget();
item.calculateSize();
items = addItem(items, item, position);
}
/**
* Generates false mouse event.
*
* @return False mouse event
*/
private MouseEvent generateFalseMouseEvent()
{
if (isDisposed())
{
return null;
}
Point cursPos = Display.getDefault().getCursorLocation();
Point contPos = toControl(cursPos);
Event event = new Event();
event.display = Display.getDefault();
event.widget = this;
MouseEvent mouseEvent = new MouseEvent(event);
mouseEvent.x = contPos.x;
mouseEvent.y = contPos.y;
return mouseEvent;
}
/**
* Returns items count.
*
* @return Items count
*/
public int getItemCount()
{
checkWidget();
if (items == null)
{
return 0;
}
return items.length;
}
/**
* Returns index of item currently under specific position. If no item
* found, -1 will be returned.
*
* @param x Left position
* @param y Top position
* @return Item index
*/
private int getItemIndexUnderCursor(int x, int y)
{
int[] visibleItems = getVisibleItems();
int start = INDENT;
for (int i : visibleItems)
{
ToolbarItem item = items[i];
int left = start;
if (i == rightAlignedIndex)
{
left = getClientArea().width - INDENT - item.getWidth();
}
else
{
start += item.getWidth();
}
int end = left + item.getWidth();
if (x >= left && x < end && y >= INDENT && y <= getClientArea().height - INDENT)
{
return i;
}
}
return -1;
}
/**
* Returns mode of widget (Vista/7)
*
* @return Mode
*/
public int getMode()
{
return mode;
}
/**
* Returns visibile items.
*
* @return Array of indexes
*/
private int[] getVisibleItems()
{
return getVisibleItems(false);
}
/**
* Returns visible items, additionally can collect hidden items to list.
*
* @param collectHidden Collect hidden items
* @return Array of indexes
*/
private int[] getVisibleItems(boolean collectHidden)
{
if (items == null)
{
return null;
}
if (collectHidden)
{
hiddenItemsList.clear();
}
int index = 0;
int hiddenItems = 0;
int currentWidth = INDENT;
int maxWidth = getClientArea().width - INDENT;
if (rightAlignedIndex != -1)
{
maxWidth -= items[rightAlignedIndex].getWidth();
}
if (chevronAdded)
{
maxWidth -= items[chevronIndex].getWidth();
}
int lastItemIndex = -1;
List<Integer> list = new ArrayList<Integer>();
while (index < items.length)
{
ToolbarItem item = items[index];
if (!item.isVisible())
{
index++;
continue;
}
if (rightAlignedIndex == index)
{
lastItemIndex = index;
}
else
{
currentWidth += item.getWidth();
if (chevronAdded && index > items.length - 4 && index < items.length - 2 && item.getStyle() != CHEVRON)
{
ToolbarItem nextItem;
if (rightAlignedIndex == index + 1)
{
nextItem = items[index + 2];
}
else
{
nextItem = items[index + 1];
}
if (nextItem.getStyle() == CHEVRON)
{
maxWidth += nextItem.getWidth();
}
}
if (currentWidth > maxWidth && item.getStyle() != CHEVRON)
{
if (collectHidden)
{
hiddenItemsList.add(index);
}
index++;
hiddenItems++;
continue;
}
list.add(index);
}
index++;
}
if (hiddenItems > 0 && !chevronAdded)
{
addChevron();
list.add(items.length - 1);
}
else if (hiddenItems == 0 && chevronAdded)
{
removeChevron();
if (itemUnderCursor == chevronIndex)
{
itemUnderCursor = -1;
}
list.remove(list.indexOf(chevronIndex));
chevronIndex = -1;
}
if (lastItemIndex != -1)
{
list.add(lastItemIndex);
}
int[] result = new int[list.size()];
for (int i = 0; i < list.size(); i++)
{
result[i] = list.get(i);
}
return result;
}
/**
* Handles key pressed event
*
* @param e KeyEvent
*/
private void handleKeyPressed(KeyEvent e)
{
if (e.stateMask != 0)
{
return;
}
int newSelectedItemIndex = -1;
int[] visibleItems = getVisibleItems();
if (visibleItems.length == 0)
{
return;
}
if (selectedItemIndex == -1)
{
if (e.keyCode == SWT.ARROW_LEFT)
{
newSelectedItemIndex = visibleItems[0];
}
else if (e.keyCode == SWT.ARROW_RIGHT)
{
newSelectedItemIndex = visibleItems[visibleItems.length - 1];
}
}
else
{
int visibleSelectedIndex = -1;
for (int i = 0; i < visibleItems.length; i++)
{
if (visibleItems[i] == selectedItemIndex)
{
visibleSelectedIndex = i;
}
}
if (e.keyCode == SWT.ARROW_LEFT)
{
int newSelectedVisibleIndex = visibleSelectedIndex - 1;
if (newSelectedVisibleIndex < 0)
{
newSelectedVisibleIndex = visibleItems.length - 1;
}
newSelectedItemIndex = visibleItems[newSelectedVisibleIndex];
}
else if (e.keyCode == SWT.ARROW_RIGHT)
{
int newSelectedVisibleIndex = visibleSelectedIndex + 1;
if (newSelectedVisibleIndex > visibleItems.length - 1)
{
newSelectedVisibleIndex = 0;
}
newSelectedItemIndex = visibleItems[newSelectedVisibleIndex];
}
else if (e.keyCode == ' ' || e.keyCode == SWT.CR || e.keyCode == SWT.LF || e.keyCode == SWT.KEYPAD_CR)
{
// TODO TBD
items[selectedItemIndex].setPushedDown(true);
items[selectedItemIndex].setHovered(true);
redraw();
return;
}
else
{
return;
}
}
if (selectedItemIndex == newSelectedItemIndex)
{
return;
}
if (selectedItemIndex >= 0)
{
items[selectedItemIndex].setSelected(false);
}
items[newSelectedItemIndex].setSelected(true);
selectedItemIndex = newSelectedItemIndex;
redraw();
}
/**
* Handles key released event.
*
* @param e KeyEvent
*/
private void handleKeyReleased(KeyEvent e)
{
if (selectedItemIndex == -1)
{
return;
}
if (e.keyCode == ' ' || e.keyCode == SWT.CR || e.keyCode == SWT.LF || e.keyCode == SWT.KEYPAD_CR)
{
items[selectedItemIndex].setPushedDown(false);
items[selectedItemIndex].setHovered(false);
redraw();
SelectionListener selectionListener = items[selectedItemIndex].getSelectionListener();
if (selectionListener == null)
{
return;
}
Event event = new Event();
event.widget = this;
selectionListener.widgetSelected(new SelectionEvent(event));
}
}
/**
* Handles mouse down event.
*
* @param e MouseEvent
*/
private void handleMouseDown(MouseEvent e)
{
if (e.x < 0 || e.y < 0 || e.x > getClientArea().width || e.y > getClientArea().height || itemUnderCursor == -1)
{
return;
}
if (selectedItemIndex >= 0)
{
items[selectedItemIndex].setHovered(false);
items[selectedItemIndex].setPushedDown(false);
items[selectedItemIndex].setSelected(false);
}
buttonPushed = true;
selectedItemIndex = getItemIndexUnderCursor(e.x, e.y);
if (selectedItemIndex >= 0)
{
items[selectedItemIndex].setPushedDown(true);
}
redraw();
}
/**
* Handles mouse hover event.
*
* @param e MouseEvent
*/
private void handleMouseHover(MouseEvent e)
{
int oldItem = itemUnderCursor;
if (e.x < 0 || e.y < 0 || e.x > getClientArea().width || e.y > getClientArea().height)
{
itemUnderCursor = -1;
}
else
{
itemUnderCursor = getItemIndexUnderCursor(e.x, e.y);
}
if (buttonPushed)
{
if (selectedItemIndex >= 0)
{
items[selectedItemIndex].setHovered(true);
items[selectedItemIndex].setPushedDown(selectedItemIndex == itemUnderCursor);
}
redraw();
return;
}
if (oldItem != itemUnderCursor)
{
if (oldItem >= 0)
{
items[oldItem].setHovered(false);
items[oldItem].setPushedDown(false);
}
if (itemUnderCursor >= 0)
{
items[itemUnderCursor].setHovered(true);
if (buttonPushed)
{
items[itemUnderCursor].setPushedDown(true);
}
}
redraw();
}
}
/**
* Handles mouse up event.
*
* @param e MouseEvent
*/
private void handleMouseUp(MouseEvent e)
{
if (!buttonPushed)
{
return;
}
buttonPushed = false;
if (selectedItemIndex == -1)
{
return;
}
if (selectedItemIndex != itemUnderCursor)
{
items[selectedItemIndex].setHovered(false);
}
items[selectedItemIndex].setSelected(true);
if (e.x < 0 || e.y < 0 || e.x > getClientArea().width || e.y > getClientArea().height || itemUnderCursor == -1)
{
if (selectedItemIndex != itemUnderCursor)
{
redraw();
}
return;
}
items[selectedItemIndex].setPushedDown(false);
items[itemUnderCursor].setHovered(true);
redraw();
int selectedItemIndexFromUp = getItemIndexUnderCursor(e.x, e.y);
if (selectedItemIndexFromUp != selectedItemIndex)
{
return;
}
SelectionListener selectionListener = items[selectedItemIndex].getSelectionListener();
if (selectionListener == null)
{
return;
}
Event event = new Event();
event.widget = this;
selectionListener.widgetSelected(new SelectionEvent(event));
MouseEvent newEvent = generateFalseMouseEvent();
if (newEvent != null && getItemIndexUnderCursor(newEvent.x, newEvent.y) != selectedItemIndexFromUp)
{
items[selectedItemIndex].setHovered(false);
redraw();
}
}
/**
* Paint widget on graphical canvas.
*
* @param gc GC
*/
private void paint(GC gc)
{
if (mode == MODE_VISTA)
{
paintVista(gc);
}
else if (mode == MODE_SEVEN)
{
paintSeven(gc);
}
paintItems(gc);
}
/**
* Paint items on graphical canvas.
*
* @param gc GC
*/
private void paintItems(GC gc)
{
int[] visibleItems = getVisibleItems();
int x = INDENT;
int y = INDENT;
for (int i = 0; i < visibleItems.length; i++)
{
ToolbarItem item = items[visibleItems[i]];
if (visibleItems[i] == rightAlignedIndex)
{
item.paint(gc, getClientArea().width - INDENT - item.getWidth(), y);
continue;
}
if (item.getStyle() == CHEVRON)
{
chevronPosition = x;
}
item.paint(gc, x, y);
x += item.getWidth();
}
}
/**
* Paint widgets using Windows Seven mode on graphical canvas.
*
* @param gc GC
*/
private void paintSeven(GC gc)
{
Color defaultForeground = gc.getForeground();
Color defaultBackground = gc.getBackground();
Rectangle rect = getClientArea();
Device device = gc.getDevice();
Color c1 = new Color(device, 249, 252, 255);
Color c2 = new Color(device, 230, 240, 250);
Color c3 = new Color(device, 220, 230, 244);
Color c4 = new Color(device, 221, 233, 247);
Color ca = new Color(device, 205, 218, 234);
Color cb = new Color(device, 160, 175, 195);
int middle = (int) Math.ceil(rect.height / 2);
Pattern patternBg1 = new Pattern(device, 0, 0, 0, middle, c1, c2);
gc.setBackgroundPattern(patternBg1);
gc.fillRectangle(new Rectangle(0, 0, rect.width, middle));
gc.setBackgroundPattern(null);
Pattern patternBg2 = new Pattern(device, 0, middle, 0, rect.height - middle, c3, c4);
gc.setBackgroundPattern(patternBg2);
gc.fillRectangle(new Rectangle(0, middle, rect.width, rect.height - middle));
gc.setBackgroundPattern(null);
gc.setForeground(ca);
gc.drawLine(0, rect.height - 2, rect.width - 1, rect.height - 2);
gc.setForeground(cb);
gc.drawLine(0, rect.height - 1, rect.width - 1, rect.height - 1);
gc.setForeground(defaultForeground);
gc.setBackground(defaultBackground);
gc.setAlpha(255);
c1.dispose();
c2.dispose();
c3.dispose();
c4.dispose();
ca.dispose();
cb.dispose();
}
/**
* Paint widget using Windows Vista mode on graphical canvas.
*
* @param gc GC
*/
private void paintVista(GC gc)
{
Color defaultForeground = gc.getForeground();
Color defaultBackground = gc.getBackground();
Rectangle rect = getClientArea();
Device device = gc.getDevice();
Color c1 = new Color(device, 5, 72, 117);
Color c2 = new Color(device, 25, 108, 119);
Color c3 = new Color(device, 28, 122, 134);
Color wh = getDisplay().getSystemColor(SWT.COLOR_WHITE);
int middle = (int) Math.ceil(rect.height / 2);
Pattern patternBg1 = new Pattern(device, 0, 0, rect.width, middle, c1, 255, c3, 255);
gc.setBackgroundPattern(patternBg1);
gc.fillRectangle(new Rectangle(0, 0, rect.width, middle));
gc.setBackgroundPattern(null);
Pattern patternBg2 = new Pattern(device, 0, middle, rect.width, rect.height - middle, c1, 255, c2, 255);
gc.setBackgroundPattern(patternBg2);
gc.fillRectangle(new Rectangle(0, middle, rect.width, rect.height - middle));
gc.setBackgroundPattern(null);
Pattern patternTopGrad = new Pattern(device, 0, 0, 0, middle, wh, 120, wh, 50);
gc.setBackgroundPattern(patternTopGrad);
gc.fillRectangle(new Rectangle(0, 0, rect.width, middle));
gc.setBackgroundPattern(null);
Pattern patternBtmGrad = new Pattern(device, 0, middle + 5, 0, rect.height, c1, 0, wh, 125);
gc.setBackgroundPattern(patternBtmGrad);
gc.fillRectangle(new Rectangle(0, middle + 5, rect.width, rect.height));
gc.setBackgroundPattern(null);
gc.setAlpha(125);
gc.setForeground(getDisplay().getSystemColor(SWT.COLOR_WHITE));
gc.drawPolygon(new int[]{0, 0, rect.width - 1, 0, rect.width - 1, rect.height - 2, 0, rect.height - 2});
gc.setAlpha(200);
gc.setForeground(getDisplay().getSystemColor(SWT.COLOR_BLACK));
gc.drawLine(0, rect.height - 1, rect.width - 1, rect.height - 1);
gc.setForeground(defaultForeground);
gc.setBackground(defaultBackground);
gc.setAlpha(255);
c1.dispose();
c2.dispose();
c3.dispose();
patternBg1.dispose();
patternBg2.dispose();
patternTopGrad.dispose();
patternBtmGrad.dispose();
}
/**
* Removes chevron item.
*/
private void removeChevron()
{
removeItem(chevronIndex);
if (selectedItemIndex == chevronIndex)
{
selectedItemIndex = -1;
}
chevronAdded = false;
}
/**
* Removes item on specific position.
*
* @param position Position to remove
*/
public void removeItem(int position)
{
items = removeItem(items, position);
}
/**
* Sets widget mode.
*
* @param mode Mode
*/
public void setMode(int mode)
{
this.mode = mode;
redraw();
}
/**
* Sets right-aligned item.
*
* @param index Right-aligned item index
*/
public void setRightAligned(int index)
{
rightAlignedIndex = index;
redraw();
}
/**
* Chevron Action
*/
private class ChevronAction implements SelectionListener
{
public void widgetSelected(SelectionEvent e)
{
getVisibleItems(true);
final Menu menu = new Menu(getShell(), SWT.POP_UP);
menu.addMenuListener(new MenuAdapter()
{
@Override
public void menuShown(MenuEvent e)
{
items[selectedItemIndex].setHovered(false);
items[selectedItemIndex].setSelected(true);
redraw();
}
});
for (int index : hiddenItemsList)
{
ToolbarItem item = items[index];
MenuItem menuItem = new MenuItem(menu, SWT.PUSH);
menuItem.setText(item.getText());
menuItem.setImage(item.getImage());
if (item.getSelectionListener() != null)
{
menuItem.addSelectionListener(item.getSelectionListener());
}
}
Point pos = toDisplay(chevronPosition, getClientArea().height);
pos.y -= 3;
menu.setLocation(pos.x, pos.y);
menu.setVisible(true);
}
public void widgetDefaultSelected(SelectionEvent e)
{
}
}
/**
* Add object to array on specific index.
*
* @param <T> Type of objects
* @param array Array of T objects
* @param object Object
* @param index Index
* @return Modified array of T objects
*/
private <T> T[] addItem(T[] array, T object, int index)
{
int length = 0;
if (array == null)
{
return null;
}
length = array.length;
@SuppressWarnings("unchecked")
T[] newArray = (T[]) Array.newInstance(array.getClass().getComponentType(), length + 1);
if (array != null)
{
System.arraycopy(array, 0, newArray, 0, length);
}
if (index != -1)
{
for (int i = newArray.length - 2; i >= index; i--)
{
if (i >= 0)
{
newArray[i + 1] = newArray[i];
}
}
newArray[index] = object;
}
else
{
newArray[newArray.length - 1] = object;
}
return newArray;
}
/**
* Removes object from array on specific index.
*
* @param <T> Type of objects
* @param array Array of T objects
* @param index Index
* @return Modified array of T objects
*/
private <T> T[] removeItem(T[] array, int index)
{
if (array == null)
{
return null;
}
@SuppressWarnings("unchecked")
T[] newArray = (T[]) Array.newInstance(array.getClass().getComponentType(), array.length - 1);
if (index > 0)
{
System.arraycopy(array, 0, newArray, 0, index);
}
if (index + 1 < array.length)
{
System.arraycopy(array, index + 1, newArray, index, newArray.length - index);
}
return newArray;
}
}