/*******************************************************************************
* Copyright (c) 2006-2007 Nicolas Richeton.
* 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 :
* Nicolas Richeton (nicolas.richeton@gmail.com) - initial API and implementation
* Richard Michalsky - bug 197959
*******************************************************************************/
package org.eclipse.nebula.widgets.gallery;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Item;
/**
* <p>
* Abstract class which provides low-level support for a grid-based group.
* renderer.
* </p>
* <p>
* NOTE: THIS WIDGET AND ITS API ARE STILL UNDER DEVELOPMENT.
* </p>
*
* @author Nicolas Richeton (nicolas.richeton@gmail.com)
* @contributor Richard Michalsky (bug 197959)
* @contributor Robert Handschmann (bug 215817)
*/
public abstract class AbstractGridGroupRenderer extends
AbstractGalleryGroupRenderer {
static final int DEFAULT_SIZE = 96;
protected int minMargin;
protected int margin;
protected boolean autoMargin;
protected int itemWidth = DEFAULT_SIZE;
protected int itemHeight = DEFAULT_SIZE;
public static final String H_COUNT = "g.h"; //$NON-NLS-1$
public static final String V_COUNT = "g.v"; //$NON-NLS-1$
protected static final String EMPTY_STRING = ""; //$NON-NLS-1$
private static final int END = 0;
private static final int START = 1;
/**
* If true, groups are always expanded and toggle button is not displayed
*/
private boolean alwaysExpanded = false;
public void draw(GC gc, GalleryItem group, int x, int y, int clipX,
int clipY, int clipWidth, int clipHeight) {
}
public GalleryItem getItem(GalleryItem group, Point coords) {
return null;
}
public Rectangle getSize(GalleryItem item) {
return null;
}
public void layout(GC gc, GalleryItem group) {
}
/**
* If true, groups are always expanded and toggle button is not displayed
*
* @return true if groups are always expanded
*/
public boolean isAlwaysExpanded() {
return alwaysExpanded;
}
/**
* Return item expand state (item.isExpanded()) Returns always true is
* alwaysExpanded is set to true.
*
* @param item
* @return
*/
protected boolean isGroupExpanded(GalleryItem item) {
if (alwaysExpanded)
return true;
if (item == null)
return false;
return item.isExpanded();
}
/**
* If true, groups are always expanded and toggle button is not displayed if
* false, expand status depends on each item.
*
* @param alwaysExpanded
*/
public void setAlwaysExpanded(boolean alwaysExpanded) {
this.alwaysExpanded = alwaysExpanded;
}
public int getMinMargin() {
return minMargin;
}
public int getItemWidth() {
return itemWidth;
}
public void setItemWidth(int itemWidth) {
this.itemWidth = itemWidth;
updateGallery();
}
public int getItemHeight() {
return itemHeight;
}
public void setItemHeight(int itemHeight) {
this.itemHeight = itemHeight;
updateGallery();
}
private void updateGallery() {
// Update gallery
if (gallery != null) {
gallery.updateStructuralValues(null, true);
gallery.updateScrollBarsProperties();
gallery.redraw();
}
}
public void setItemSize(int width, int height) {
this.itemHeight = height;
this.itemWidth = width;
updateGallery();
}
public void setMinMargin(int minMargin) {
this.minMargin = minMargin;
updateGallery();
}
public boolean isAutoMargin() {
return autoMargin;
}
public void setAutoMargin(boolean autoMargin) {
this.autoMargin = autoMargin;
updateGallery();
}
protected int calculateMargins(int size, int count, int itemSize) {
int margin = this.minMargin;
margin += Math
.round((float) (size - this.minMargin - (count * (itemSize + this.minMargin)))
/ (count + 1));
return margin;
}
protected Point getSize(int nbx, int nby, int itemSizeX, int itemSizeY,
int minMargin, int autoMargin) {
int x = 0, y = 0;
if (gallery.isVertical()) {
x = nbx * itemSizeX + (nbx - 1) * margin + 2 * minMargin;
y = nby * itemSizeY + nby * minMargin;
} else {
x = nbx * itemSizeX + nbx * minMargin;
y = nby * itemSizeY + (nby - 1) * margin + 2 * minMargin;
}
return new Point(x, y);
}
/**
* Draw a child item. Only used when useGroup is true.
*
* @param gc
* @param index
* @param selected
* @param parent
*/
protected void drawItem(GC gc, int index, boolean selected,
GalleryItem parent, int offsetY) {
if (Gallery.DEBUG)
System.out.println("Draw item ? " + index); //$NON-NLS-1$
if (index < parent.getItemCount()) {
int hCount = ((Integer) parent.getData(H_COUNT)).intValue();
int vCount = ((Integer) parent.getData(V_COUNT)).intValue();
if (Gallery.DEBUG)
System.out.println("hCount : " + hCount + " vCount : " //$NON-NLS-1$//$NON-NLS-2$
+ vCount);
int posX, posY;
if (gallery.isVertical()) {
posX = index % hCount;
posY = (index - posX) / hCount;
} else {
posY = index % vCount;
posX = (index - posY) / vCount;
}
Item item = parent.getItem(index);
// No item ? return
if (item == null)
return;
GalleryItem gItem = (GalleryItem) item;
int xPixelPos, yPixelPos;
if (gallery.isVertical()) {
xPixelPos = posX * (itemWidth + margin) + margin;
yPixelPos = posY * (itemHeight + minMargin) - gallery.translate
/* + minMargin */
+ ((parent == null) ? 0 : (parent.y) + offsetY);
gItem.x = xPixelPos;
gItem.y = yPixelPos + gallery.translate;
} else {
xPixelPos = posX * (itemWidth + minMargin) - gallery.translate
/* + minMargin */
+ ((parent == null) ? 0 : (parent.x) + offsetY);
yPixelPos = posY * (itemHeight + margin) + margin;
gItem.x = xPixelPos + gallery.translate;
gItem.y = yPixelPos;
}
gItem.height = itemHeight;
gItem.width = itemWidth;
gallery.sendPaintItemEvent(item, index, gc, xPixelPos, yPixelPos,
this.itemWidth, this.itemHeight);
if (gallery.getItemRenderer() != null) {
// gc.setClipping(xPixelPos, yPixelPos, itemWidth, itemHeight);
gallery.getItemRenderer().setSelected(selected);
if (Gallery.DEBUG)
System.out.println("itemRender.draw"); //$NON-NLS-1$
Rectangle oldClipping = gc.getClipping();
gc.setClipping(oldClipping.intersection(new Rectangle(
xPixelPos, yPixelPos, itemWidth, itemHeight)));
gallery.getItemRenderer().draw(gc, gItem, index, xPixelPos,
yPixelPos, itemWidth, itemHeight);
gc.setClipping(oldClipping);
if (Gallery.DEBUG)
System.out.println("itemRender done"); //$NON-NLS-1$
}
}
}
protected int[] getVisibleItems(GalleryItem group, int x, int y, int clipX,
int clipY, int clipWidth, int clipHeight, int offset) {
int[] indexes;
if (gallery.isVertical()) {
int count = ((Integer) group.getData(H_COUNT)).intValue();
// TODO: Not used ATM
// int vCount = ((Integer) group.getData(V_COUNT)).intValue();
int firstLine = (clipY - y - offset - minMargin)
/ (itemHeight + minMargin);
if (firstLine < 0)
firstLine = 0;
int firstItem = firstLine * count;
if (Gallery.DEBUG)
System.out.println("First line : " + firstLine); //$NON-NLS-1$
int lastLine = (clipY - y - offset + clipHeight - minMargin)
/ (itemHeight + minMargin);
if (lastLine < firstLine)
lastLine = firstLine;
if (Gallery.DEBUG)
System.out.println("Last line : " + lastLine); //$NON-NLS-1$
int lastItem = (lastLine + 1) * count;
// exit if no item selected
if (lastItem - firstItem == 0)
return null;
indexes = new int[lastItem - firstItem];
for (int i = 0; i < (lastItem - firstItem); i++) {
indexes[i] = firstItem + i;
}
} else {
int count = ((Integer) group.getData(V_COUNT)).intValue();
int firstLine = (clipX - x - offset - minMargin)
/ (itemWidth + minMargin);
if (firstLine < 0)
firstLine = 0;
int firstItem = firstLine * count;
if (Gallery.DEBUG)
System.out.println("First line : " + firstLine); //$NON-NLS-1$
int lastLine = (clipX - x - offset + clipWidth - minMargin)
/ (itemWidth + minMargin);
if (lastLine < firstLine)
lastLine = firstLine;
if (Gallery.DEBUG)
System.out.println("Last line : " + lastLine); //$NON-NLS-1$
int lastItem = (lastLine + 1) * count;
// exit if no item selected
if (lastItem - firstItem == 0)
return null;
indexes = new int[lastItem - firstItem];
for (int i = 0; i < (lastItem - firstItem); i++) {
indexes[i] = firstItem + i;
}
}
return indexes;
}
/**
* Calculate how many items are displayed horizontally and vertically.
*
* @param size
* @param nbItems
* @param itemSize
* @return
*/
protected Point gridLayout(int size, int nbItems, int itemSize) {
int x = 0, y = 0;
if (nbItems == 0)
return new Point(x, y);
x = (size - minMargin) / (itemSize + minMargin);
if (x > 0) {
y = (int) Math.ceil((double) nbItems / (double) x);
} else {
// Show at least one item;
y = nbItems;
x = 1;
}
return new Point(x, y);
}
public void dispose() {
// Nothing required here. This method can be overridden when needed.
}
public boolean mouseDown(GalleryItem group, MouseEvent e, Point coords) {
return false;
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.nebula.widgets.gallery.AbstractGalleryGroupRenderer#preLayout
* (org.eclipse.swt.graphics.GC)
*/
public void preLayout(GC gc) {
// Reset margin to minimal value before "best fit" calculation
this.margin = this.minMargin;
super.preLayout(gc);
}
protected Point getLayoutData(GalleryItem item) {
Integer hCount = ((Integer) item.getData(H_COUNT));
Integer vCount = ((Integer) item.getData(V_COUNT));
if (hCount == null || vCount == null)
return null;
return new Point(hCount.intValue(), vCount.intValue());
}
protected Rectangle getSize(GalleryItem item, int offsetY) {
GalleryItem parent = item.getParentItem();
if (parent != null) {
int index = parent.indexOf(item);
Point layoutData = getLayoutData(parent);
if (layoutData == null)
return null;
int hCount = layoutData.x;
int vCount = layoutData.y;
if (Gallery.DEBUG)
System.out.println("hCount : " + hCount + " vCount : " //$NON-NLS-1$ //$NON-NLS-2$
+ vCount);
if (gallery.isVertical()) {
int posX = index % hCount;
int posY = (index - posX) / hCount;
int xPixelPos = posX * (itemWidth + margin) + margin;
int yPixelPos = posY * (itemHeight + minMargin)
+ ((parent == null) ? 0 : (parent.y) + offsetY);
return new Rectangle(xPixelPos, yPixelPos, this.itemWidth,
this.itemHeight);
}
// gallery is horizontal
int posY = index % vCount;
int posX = (index - posY) / vCount;
int yPixelPos = posY * (itemHeight + margin) + margin;
int xPixelPos = posX * (itemWidth + minMargin)
+ ((parent == null) ? 0 : (parent.x) + offsetY);
return new Rectangle(xPixelPos, yPixelPos, this.itemWidth,
this.itemHeight);
}
return null;
}
/**
* Get item at pixel position
*
* @param coords
* @return
*/
protected GalleryItem getItem(GalleryItem group, Point coords, int offsetY) {
if (Gallery.DEBUG) {
System.out.println("getitem " + coords.x + " " + coords.y); //$NON-NLS-1$//$NON-NLS-2$
}
int itemNb;
if (gallery.isVertical()) {
Integer tmp = (Integer) group.getData(H_COUNT);
if (tmp == null)
return null;
int hCount = tmp.intValue();
// Calculate where the item should be if it exists
int posX = (coords.x - margin) / (itemWidth + margin);
// Check if the users clicked on the X margin.
int posOnItem = (coords.x - margin) % (itemWidth + margin);
if (posOnItem > itemWidth || posOnItem < 0) {
return null;
}
if (posX >= hCount) // Nothing there
return null;
if (coords.y - group.y < offsetY)
return null;
int posY = (coords.y - group.y - offsetY)
/ (itemHeight + minMargin);
// Check if the users clicked on the Y margin.
if (((coords.y - group.y - offsetY) % (itemHeight + minMargin)) > itemHeight) {
return null;
}
itemNb = posX + posY * hCount;
} else {
Integer tmp = (Integer) group.getData(V_COUNT);
if (tmp == null)
return null;
int vCount = tmp.intValue();
// Calculate where the item should be if it exists
int posY = (coords.y - margin) / (itemHeight + margin);
// Check if the users clicked on the X margin.
int posOnItem = (coords.y - margin) % (itemHeight + margin);
if (posOnItem > itemHeight || posOnItem < 0) {
return null;
}
if (posY >= vCount) // Nothing there
return null;
if (coords.x - group.x < offsetY)
return null;
int posX = (coords.x - group.x - offsetY) / (itemWidth + minMargin);
// Check if the users clicked on the X margin.
if (((coords.x - group.x - offsetY) % (itemWidth + minMargin)) > itemWidth) {
return null;
}
itemNb = posY + posX * vCount;
}
if (Gallery.DEBUG) {
System.out.println("Item found : " + itemNb); //$NON-NLS-1$
}
if (itemNb < group.getItemCount()) {
return group.getItem(itemNb);
}
return null;
}
private GalleryItem goLeft(GalleryItem group, int posParam) {
int pos = posParam - 1;
if (pos < 0) {
// Look for next non-empty group and get the last item
GalleryItem item = null;
GalleryItem currentGroup = group;
while (item == null && currentGroup != null) {
currentGroup = this.getPreviousGroup(currentGroup);
item = this.getFirstItem(currentGroup, END);
}
return item;
}
// else
return group.getItem(pos);
}
private GalleryItem goRight(GalleryItem group, int posParam) {
int pos = posParam + 1;
if (pos >= group.getItemCount()) {
// Look for next non-empty group and get the first item
GalleryItem item = null;
GalleryItem currentGroup = group;
while (item == null && currentGroup != null) {
currentGroup = this.getNextGroup(currentGroup);
item = this.getFirstItem(currentGroup, START);
}
return item;
}
// else
return group.getItem(pos);
}
private GalleryItem goUp(GalleryItem group, int posParam, int hCount,
int lineCount) {
if (lineCount == 0) {
return null;
}
// Optimization when only one group involved
if (posParam - hCount * lineCount >= 0) {
return group.getItem(posParam - hCount * lineCount);
}
// Get next item.
GalleryItem next = goUp(group, posParam, hCount);
if (next == null) {
return null;
}
GalleryItem newItem = null;
for (int i = 1; i < lineCount; i++) {
newItem = goUp(next.getParentItem(), next.getParentItem().indexOf(
next), hCount);
if (newItem == next || newItem == null) {
break;
}
next = newItem;
}
return next;
}
private GalleryItem goDown(GalleryItem group, int posParam, int hCount,
int lineCount) {
if (lineCount == 0) {
return null;
}
// Optimization when only one group involved
if (posParam + hCount * lineCount < group.getItemCount()) {
return group.getItem(posParam + hCount * lineCount);
}
// Get next item.
GalleryItem next = goDown(group, posParam, hCount);
if (next == null) {
return null;
}
GalleryItem newItem = null;
for (int i = 1; i < lineCount; i++) {
newItem = goDown(next.getParentItem(), next.getParentItem()
.indexOf(next), hCount);
if (newItem == next || newItem == null) {
break;
}
next = newItem;
}
return next;
}
/**
* Get the next item, when going up.
*
* @param group
* current group
* @param posParam
* index of currently selected item
* @param hCount
* size of a line
* @return
*/
private GalleryItem goUp(GalleryItem group, int posParam, int hCount) {
int colPos = posParam % hCount;
int pos = posParam - hCount;
if (pos < 0) {
// Look for next non-empty group and get the last item
GalleryItem item = null;
GalleryItem currentGroup = group;
while (item == null && currentGroup != null) {
currentGroup = this.getPreviousGroup(currentGroup);
item = this.getItemAt(currentGroup, colPos, END);
}
return item;
}
// else
return group.getItem(pos);
}
/**
* Get the next item, when going down.
*
* @param group
* current group
* @param posParam
* index of currently selected item
* @param hCount
* size of a line
* @return
*/
private GalleryItem goDown(GalleryItem group, int posParam, int hCount) {
int colPos = posParam % hCount;
int pos = posParam + hCount;
if (pos >= group.getItemCount()) {
// Look for next non-empty group and get the first item
GalleryItem item = null;
GalleryItem currentGroup = group;
while (item == null && currentGroup != null) {
currentGroup = this.getNextGroup(currentGroup);
item = this.getItemAt(currentGroup, colPos, START);
}
return item;
}
// else
return group.getItem(pos);
}
/**
* Get maximum visible lines.
*
* @return
*/
private int getMaxVisibleLines() {
// TODO: support group titles (fewer lines are visible if one or more
// group titles are displayed). This method should probably be
// implemented in the group renderer and not in the abstract class.
// Gallery is vertical
if (gallery.isVertical()) {
return gallery.getClientArea().height / itemHeight;
}
// Gallery is horizontal
return gallery.getClientArea().width / itemWidth;
}
public GalleryItem getNextItem(GalleryItem item, int key) {
// Key navigation is useless with an empty gallery
if (gallery.getItemCount() == 0) {
return null;
}
// Check for current selection
if (item == null) {
// No current selection, select the first item
if (gallery.getItemCount() > 0) {
GalleryItem firstGroup = gallery.getItem(0);
if (firstGroup != null && firstGroup.getItemCount() > 0) {
return firstGroup.getItem(0);
}
}
return null;
}
// Check for groups
if (item.getParentItem() == null) {
// Key navigation is only available for child items ATM
return null;
}
GalleryItem group = item.getParentItem();
// Handle HOME and END
switch (key) {
case SWT.HOME:
gallery.getItem(0).setExpanded(true);
return getFirstItem(gallery.getItem(0), START);
case SWT.END:
gallery.getItem(gallery.getItemCount() - 1).setExpanded(true);
return getFirstItem(gallery.getItem(gallery.getItemCount() - 1),
END);
}
int pos = group.indexOf(item);
GalleryItem next = null;
// Handle arrows and page up / down
if (gallery.isVertical()) {
int hCount = ((Integer) group.getData(H_COUNT)).intValue();
int maxVisibleRows = getMaxVisibleLines();
switch (key) {
case SWT.ARROW_LEFT:
next = goLeft(group, pos);
break;
case SWT.ARROW_RIGHT:
next = goRight(group, pos);
break;
case SWT.ARROW_UP:
next = goUp(group, pos, hCount, 1);
break;
case SWT.ARROW_DOWN:
next = goDown(group, pos, hCount, 1);
break;
case SWT.PAGE_UP:
next = goUp(group, pos, hCount, Math.max(maxVisibleRows - 1, 1));
break;
case SWT.PAGE_DOWN:
next = goDown(group, pos, hCount, Math.max(maxVisibleRows - 1,
1));
break;
}
} else {
int vCount = ((Integer) group.getData(V_COUNT)).intValue();
int maxVisibleColumns = getMaxVisibleLines();
switch (key) {
case SWT.ARROW_LEFT:
next = goUp(group, pos, vCount);
break;
case SWT.ARROW_RIGHT:
next = goDown(group, pos, vCount);
break;
case SWT.ARROW_UP:
next = goLeft(group, pos);
break;
case SWT.ARROW_DOWN:
next = goRight(group, pos);
break;
case SWT.PAGE_UP:
next = goUp(group, pos, vCount
* Math.max(maxVisibleColumns - 1, 1));
break;
case SWT.PAGE_DOWN:
next = goDown(group, pos, vCount
* Math.max(maxVisibleColumns - 1, 1));
break;
}
}
return next;
}
private GalleryItem getPreviousGroup(GalleryItem group) {
int gPos = gallery.indexOf(group);
while (gPos > 0) {
GalleryItem newGroup = gallery.getItem(gPos - 1);
if (isGroupExpanded(newGroup))
return newGroup;
gPos--;
}
return null;
}
private GalleryItem getNextGroup(GalleryItem group) {
int gPos = gallery.indexOf(group);
while (gPos < gallery.getItemCount() - 1) {
GalleryItem newGroup = gallery.getItem(gPos + 1);
if (isGroupExpanded(newGroup))
return newGroup;
gPos++;
}
return null;
}
private GalleryItem getFirstItem(GalleryItem group, int from) {
if (group == null)
return null;
switch (from) {
case END:
return group.getItem(group.getItemCount() - 1);
case START:
default:
return group.getItem(0);
}
}
/**
* Return the child item of group which is at column 'pos' starting from
* direction. If this item doesn't exists, returns the nearest item.
*
* @param group
* @param pos
* @param from
* START or END
* @return
*/
private GalleryItem getItemAt(GalleryItem group, int pos, int from) {
if (group == null)
return null;
int hCount = ((Integer) group.getData(H_COUNT)).intValue();
int offset = 0;
switch (from) {
case END:
if (group.getItemCount() == 0)
return null;
// Last item column
int endPos = group.getItemCount() % hCount;
// If last item column is 0, the line is full
if (endPos == 0) {
endPos = hCount - 1;
offset--;
}
// If there is an item at column 'pos'
if (pos < endPos) {
int nbLines = (group.getItemCount() / hCount) + offset;
return group.getItem(nbLines * hCount + pos);
}
// Get the last item.
return group.getItem((group.getItemCount() / hCount + offset)
* hCount + endPos - 1);
case START:
default:
if (pos >= group.getItemCount())
return group.getItem(group.getItemCount() - 1);
return group.getItem(pos);
}
}
}