/*******************************************************************************
Jimm - Mobile Messaging - J2ME ICQ clone
Copyright (C) 2003-05 Jimm Project
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.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
********************************************************************************
File: src/DrawControls/VirtualList.java
Version: ###VERSION### Date: ###DATE###
Author(s): Artyomov Denis, Igor Palkin, Vladimir Kryukov
*******************************************************************************/
package jimmui.view.base;
import javax.microedition.lcdui.*;
import jimm.Jimm;
import jimmui.view.UIBuilder;
import jimmui.view.base.touch.*;
import jimmui.view.menu.*;
/**
* This class is base class of owner draw list controls
*
* It allows you to create list with different colors and images.
* Base class of VirtualDrawList if Canvas, so it draw itself when
* paint event is heppen. VirtualList have cursor controlled of
* user
*/
public abstract class VirtualList extends CanvasEx {
// Caption of VL
private int topItem = 0;
private int topOffset = 0;
protected static final byte MP_ALL = 0;
protected static final byte MP_SELECTABLE_ONLY = 1;
private byte movingPolicy = MP_ALL;
private int currItem = 0;
protected MyActionBar bar = new MyActionBar();
protected MySoftBar softBar = new MySoftBar();
private static MyScrollBar scrollBar = new MyScrollBar();
// Set of fonts for quick selecting
private Font[] fontSet;
//! Create new virtual list with default values
public VirtualList(String capt) {
setCaption(capt);
setSoftBarLabels("menu", null, "back", false);
fontSet = GraphicsEx.chatFontSet;
setSize(Jimm.getJimm().getDisplay().getScreenWidth(), Jimm.getJimm().getDisplay().getScreenHeight());
}
protected final void setSoftBarLabels(String more, String ok, String back, boolean direct) {
softBar.setSoftBarLabels(more, ok, back, direct);
}
/**
* Request number of list elements to be shown in list.
*
* You must return number of list elements in successtor of
* VirtualList. Class calls method "getSize" each time before it drawn
*/
abstract protected int getSize();
protected final Font[] getFontSet() {
return fontSet;
}
protected final Font getDefaultFont() {
return fontSet[FONT_STYLE_PLAIN];
}
protected final void setFontSet(Font[] set) {
fontSet = set;
}
// returns height of draw area in pixels
public final int getContentHeight() {
return getHeight() - bar.getHeight() - 1;
}
/** Returns height of each item in list */
protected abstract int getItemHeight(int itemIndex);
protected void onCursorMove() {
}
public final int getCurrItem() {
return currItem;
}
private void setCurrItem(int cItem) {
currItem = Math.max(0, Math.min(cItem, getSize() - 1));
}
protected boolean isItemSelectable(int index) {
return true;
}
protected void doKeyReaction(int keyCode, int actionCode, int type) {
if ((CanvasEx.KEY_REPEATED == type) || (CanvasEx.KEY_PRESSED == type)) {
navigationKeyReaction(keyCode, actionCode);
}
}
protected final int[] getScroll() {
// scroll bar
int[] scroll = MyScrollBar.makeVertScroll(
(getWidth() - scrollerWidth), bar.getHeight(),
scrollerWidth, getContentHeight() + 1,
getContentHeight(), getFullSize());
if (null != scroll) {
scroll[MyScrollBar.SCROLL_TOP_VALUE] = getTopOffset();
}
return scroll;
}
// #sijapp cond.if modules_TOUCH is "true"#
protected final void setScrollTop(int top) {
setTopByOffset(top);
invalidate();
}
protected final int getScrollTop() {
return getTopOffset();
}
protected final int getItemByCoord(int y) {
int size = getSize();
// is pointing on data area
int itemY1 = bar.getHeight() - get_TopOffset();
if (y < itemY1) {
for (int i = get_Top(); 0 <= i; --i) {
if (itemY1 <= y) {
return i;
}
itemY1 -= getItemHeight(i);
}
} else {
for (int i = get_Top(); i < size; ++i) {
itemY1 += getItemHeight(i);
if (y < itemY1) {
return i;
}
}
}
return -1;
}
protected void touchItemTaped(int item, int x, TouchState state) {
if (state.isLong) {
showMenu(getMenu());
} else if (state.isSecondTap) {
execJimmAction(NativeCanvas.JIMM_SELECT);
}
}
protected boolean touchItemPressed(int item, int x, int y) {
touchPressed = true;
if (getCurrItem() != item) {
setCurrItem(item);
onCursorMove();
invalidate();
return true;
}
return false;
}
protected final void stylusPressed(TouchState state) {
if (getHeight() < state.y) {
state.region = softBar;
return;
}
if (state.y < bar.getHeight()) {
state.region = bar;
return;
}
touchUsed = true;
int item = getItemByCoord(state.y);
if (0 <= item) {
currItem = -1;
state.prevTopY = getTopOffset();
touchItemPressed(item, state.x, state.y);
state.isSecondTap = true;
}
}
protected final void stylusGeneralYMoved(TouchState state) {
int item = getItemByCoord(state.y);
if (0 <= item) {
setTopByOffset(state.prevTopY + (state.fromY - state.y));
invalidate();
}
}
protected final void stylusTap(TouchState state) {
int item = getItemByCoord(state.y);
if (0 <= item) {
touchItemTaped(item, state.x, state);
}
}
// #sijapp cond.end#
/**
* Set caption text for list
*/
public final void setCaption(String capt) {
bar.setCaption(capt);
}
public final String getCaption() {
return bar.getCaption();
}
///////////////////////////////////////////////////////////
public final void setAllToTop() {
setTopByOffset(0);
setCurrItem(0);
}
public final void setAllToBottom() {
setCurrentItemIndex(getSize() - 1);
}
private void setTop(int item, int offset) {
set_Top(item, offset);
if (this == Jimm.getJimm().getDisplay().getNativeCanvas().getCanvas()) {
MyScrollBar.showScroll();
}
}
protected void sizeChanged(int prevW, int prevH, int w, int h) {
boolean prev = prevH < prevW;
boolean curr = h < w;
if (prev != curr) {
int delta = prevH - h;
setTopByOffset(getTopOffset() + delta);
}
}
private void setOptimalTopItem() {
int size = getSize();
if (0 == size) {
setTopByOffset(0);
return;
}
int current = Math.max(0, getCurrItem());
int top = get_Top();
int topOffset = get_TopOffset();
if (current <= top) {
top = current;
final int contentHeight = getContentHeight();
int maxTopHeight = getOffset(size) - contentHeight;
top = Math.min(top, getItemByOffset(maxTopHeight));
setTop(top, Math.max(0, getItemHeight(top) - contentHeight));
} else {
top = Math.min(top, size - 1);
int height;
int offset = getContentHeight();
for (int item = current; top <= item; --item) {
height = getItemHeight(item);
offset -= height;
if (offset <= 0) {
offset = -offset;
if (item == current) {
offset = 0;
}
if (item < top) {
} else if ((item == top) && (offset < topOffset)) {
} else {
setTop(item, offset);
}
return;
}
}
}
}
public final void setCurrentItemIndex(int current) {
int last = getCurrItem();
setCurrItem(current);
if (getCurrItem() != last) {
setOptimalTopItem();
onCursorMove();
}
}
protected int getItemByOffset(int offset) {
int size = getSize();
for (int i = 0; i < size; ++i) {
int height = getItemHeight(i);
if (offset < height) {
return i;
}
offset -= height;
}
return size;
}
public final void setTopByOffset(int offset) {
offset = Math.max(0, Math.min(offset, getFullSize() - getContentHeight()));
int top = getItemByOffset(offset);
setTop(top, offset - getOffset(top));
}
public final int getTopOffset() {
return getOffset(get_Top()) + get_TopOffset();
}
protected final int getOffset(int max) {
int height = 0;
for (int i = 0; i < max; ++i) {
height += getItemHeight(i);
}
return height;
}
public final int getFullSize() {
return getOffset(getSize());
}
///////////////////////////////////////////////////////////
protected void paint(GraphicsEx g) {
beforePaint();
int bottom = getHeight();
boolean onlySoftBar = (bottom <= g.getClipY());
if (!onlySoftBar) {
int captionHeight = bar.getHeight();
paintContent(g, captionHeight, getWidth(), getHeight() - captionHeight);
g.setClip(0, captionHeight, getWidth(), getHeight());
g.drawPopup(this, captionHeight);
bar.paint(g, this, getWidth());
}
if (isSoftBarShown()) {
softBar.paint(g, this, getHeight());
}
}
protected void beforePaint() {
}
protected final int getClientHeight() {
return getHeight() - bar.getHeight();
}
protected MenuModel getMenu() {
return null;
}
public final void showMenu(MenuModel m) {
if ((null != m) && (0 < m.count())) {
UIBuilder.createMenu(m).show();
}
}
///////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////
protected final void setMovingPolicy(byte mp) {
movingPolicy = mp;
}
byte getMovingPolicy() {
return movingPolicy;
}
private void set_Top(int item, int offset) {
topItem = item;
topOffset = offset;
}
private int get_Top() {
return topItem;
}
private int get_TopOffset() {
return topOffset;
}
protected final void paintContent(GraphicsEx g, int top, int width, int height) {
g.setClip(0, top, width, height);
drawBack(g, top, width, height);
drawItems(g, top, width, height);
}
protected void drawEmptyItems(GraphicsEx g, int top_y) {
}
private void drawBack(GraphicsEx g, int top, int width, int height) {
// Fill background
g.setThemeColor(CanvasEx.THEME_BACKGROUND);
g.fillRect(0, top, width, height);
g.setClip(0, top, width, height);
if (null != Scheme.backImage) {
int offset = 0;
if (0 < getSize()) {
offset = Math.max(0, Scheme.backImage.getHeight() - height)
* getTopOffset() / getFullSize();
}
g.drawImage(Scheme.backImage, 0, top - offset,
Graphics.LEFT | Graphics.TOP);
}
}
private void drawItems(GraphicsEx g, int top_y, int itemWidth, int height) {
int size = getSize();
int bottom = height + top_y;
if (0 == size) {
drawEmptyItems(g, top_y);
return;
}
boolean showCursor = false;
int currentY = 0;
int currentIndex = isCurrentItemSelectable() ? getCurrItem() : -1;
// #sijapp cond.if modules_TOUCH is "true"#
if (touchUsed && !touchPressed) currentIndex = -1;
// #sijapp cond.end#
{ // background
int offset = topOffset;
int y = top_y;
for (int i = topItem; i < size; ++i) {
int itemHeight = getItemHeight(i);
int realHeight = Math.min(itemHeight - offset, bottom - y + 1);
g.setClip(0, y, itemWidth, realHeight + 1);
g.setStrokeStyle(Graphics.SOLID);
if (i == currentIndex) {
currentY = y - offset;
if (g.notEqualsColor(CanvasEx.THEME_BACKGROUND, CanvasEx.THEME_SELECTION_BACK)) {
g.setThemeColor(CanvasEx.THEME_SELECTION_BACK);
g.fillRect(0, currentY, itemWidth - 1, itemHeight);
}
drawItemBack(g, i, 2, y - offset, itemWidth - 4, itemHeight, offset, realHeight);
showCursor = true;
} else {
drawItemBack(g, i, 2, y - offset, itemWidth - 4, itemHeight, offset, realHeight);
}
y += itemHeight - offset;
if (y >= bottom) break;
offset = 0;
}
if (0 < MyScrollBar.showScroll) {
g.setClip(0, top_y, itemWidth, bottom - top_y);
MyScrollBar.paint(g, this, CanvasEx.THEME_SCROLL_BACK);
}
}
{ // Draw items
g.setColor(0);
int offset = topOffset;
int y = top_y;
for (int i = topItem; i < size; ++i) {
int itemHeight = getItemHeight(i);
int realHeight = Math.min(itemHeight, bottom - y + 1);
g.setClip(0, y, itemWidth, realHeight + 1);
g.setStrokeStyle(Graphics.SOLID);
drawItemData(g, i, 2, y - offset, itemWidth - 4, itemHeight, offset, realHeight);
y += itemHeight - offset;
if (y >= bottom) break;
offset = 0;
}
}
if (showCursor) {
int itemHeight = getItemHeight(currentIndex);
g.setClip(0, currentY, itemWidth, itemHeight + 1);
g.setThemeColor(CanvasEx.THEME_SELECTION_RECT);
g.setStrokeStyle(Graphics.SOLID);
g.drawSimpleRect(0, currentY, itemWidth - 1, itemHeight);
}
}
protected void drawItemBack(GraphicsEx g,
int index, int x1, int y1, int w, int h, int skip, int to) {
}
protected abstract void drawItemData(GraphicsEx g,
int index, int x1, int y1, int w, int h, int skip, int to);
protected boolean isCurrentItemSelectable() {
return true;
}
//////////////////////////////////////////////////////////////////////////////////
private void navigationKeyReaction(int keyCode, int actionCode) {
switch (actionCode) {
case NativeCanvas.NAVIKEY_DOWN:
moveCursor(+1);
invalidate();
break;
case NativeCanvas.NAVIKEY_UP:
moveCursor(-1);
invalidate();
break;
case NativeCanvas.NAVIKEY_FIRE:
execJimmAction(NativeCanvas.JIMM_SELECT);
break;
}
switch (keyCode) {
case NativeCanvas.KEY_NUM1:
setTopByOffset(0);
setCurrentItemIndex(0);
invalidate();
break;
case NativeCanvas.KEY_NUM7:
setTopByOffset(getFullSize() - getContentHeight());
setCurrentItemIndex(getSize() - 1);
invalidate();
break;
case NativeCanvas.KEY_NUM3:
int top = getTopVisibleItem();
if (getCurrItem() == top) {
setTopByOffset(getTopOffset() - getContentHeight() * 9 / 10);
top = getTopVisibleItem();
}
setCurrentItemIndex(top);
invalidate();
break;
case NativeCanvas.KEY_NUM9:
int bottom = getBottomVisibleItem();
if (getCurrItem() == bottom) {
setTopByOffset(getTopOffset() + getContentHeight() * 9 / 10);
bottom = getBottomVisibleItem();
}
setCurrentItemIndex(bottom);
invalidate();
break;
}
}
private void moveCursor(int step) {
int top = getTopOffset();
int visible = getContentHeight();
// #sijapp cond.if modules_TOUCH is "true"#
if (touchUsed) {
touchUsed = false;
int curr = getCurrItem();
int current = getOffset(curr);
if ((current + getItemHeight(curr) < top) || (top + visible < current)) {
int offset = (step < 0) ? (top + visible - 1) : (top + 1);
setCurrentItemIndex(getItemByOffset(offset));
return;
}
}
// #sijapp cond.end#
int next = getCurrItem() + step;
if (VirtualList.MP_SELECTABLE_ONLY == getMovingPolicy()) {
while (!isItemSelectable(next)) {
next += step;
if ((next < 0) || (getSize() <= next)) {
break;
}
}
}
next = Math.max(-1, Math.min(next, getSize()));
if (0 < step) {
if (getSize() == next) {
int end = getFullSize() - visible;
if (top < end) {
setTopByOffset(Math.min(end, top + visible / 3));
return;
}
} else {
int nextOffset = getOffset(next);
if (top + visible < nextOffset) {
setTopByOffset(top + visible / 3);
return;
}
}
} else {
if (-1 == next) {
if (0 < top) {
setTopByOffset(Math.max(0, top - visible / 3));
return;
}
} else {
if (getOffset(next) + getItemHeight(next) < top) {
setTopByOffset(top - visible / 3);
return;
}
}
}
if ((next < 0) || (getSize() <= next)) {
return;
}
setCurrentItemIndex(next);
}
private int getTopVisibleItem() {
int size = getSize();
int cur = get_Top();
int offset = get_TopOffset();
if ((cur + 1 < size) && (0 < offset)) {
int used = getItemHeight(cur) - offset;
int height = getContentHeight() - used;
if ((getItemHeight(cur + 1) < height) || (used < 5)) {
cur++;
}
}
return cur;
}
private int getBottomVisibleItem() {
int size = getSize();
int cur = size;
int offset = getContentHeight() + get_TopOffset();
for (int i = get_Top(); i < size; ++i) {
int height = getItemHeight(i);
if (offset < height) {
cur = i;
break;
}
offset -= height;
}
cur = (size == cur) ? size - 1 : Math.max(get_Top(), cur - 1);
return cur;
}
}