/*
GNU General Public License
CacheWolf is a software for PocketPC, Win and Linux that
enables paperless caching.
It supports the sites geocaching.com and opencaching.de
Copyright (C) 2006 CacheWolf development team
See http://www.cachewolf.de/ for more information.
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; version 2 of the License.
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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
package CacheWolf;
import CacheWolf.controls.GuiImageBroker;
import CacheWolf.database.CacheDB;
import CacheWolf.database.CacheHolder;
import CacheWolf.database.CacheSize;
import CacheWolf.database.CacheType;
import CacheWolf.utils.Common;
import CacheWolf.utils.MyLocale;
import ewe.fx.Color;
import ewe.fx.Dimension;
import ewe.fx.Font;
import ewe.fx.FontMetrics;
import ewe.fx.Graphics;
import ewe.fx.IconAndText;
import ewe.fx.Image;
import ewe.fx.Point;
import ewe.fx.Rect;
import ewe.fx.mImage;
import ewe.graphics.AniImage;
import ewe.graphics.ImageDragContext;
import ewe.graphics.InteractivePanel;
import ewe.sys.Vm;
import ewe.ui.CellConstants;
import ewe.ui.CellPanel;
import ewe.ui.Control;
import ewe.ui.ControlEvent;
import ewe.ui.DragContext;
import ewe.ui.Event;
import ewe.ui.Gui;
import ewe.ui.PenEvent;
import ewe.ui.mButton;
import ewe.ui.mList;
/**
* The radar panel. Displays the caches around a centre point.<br>
* Handles scaling as well as toggling the different views in the radar panel.<br>
* Also handles clicking on a cache.<br>
* Class ID=500
*/
public class RadarPanel extends CellPanel {
mButton btnMinus;
mButton btnToggle;
mButton btnPlus;
int toggleMod = 0; // 0 = cacheicons, 1= cacheWP, 2 = cacheNames
CacheDB cacheDB;
MyInteractivePanel iActP;
double scale;
int scaleKm = 2;
int centerX, centerY;
int height, width;
CacheHolder selectedWaypoint = null;
boolean penMoving = false;
int x1, y1, x2, y2 = 0;
boolean reCenterImage = true;
private final static Color RED = new Color(255, 0, 0);
private final static Color GREEN = new Color(0, 255, 0);
private final static Color YELLOW = new Color(255, 255, 0);
/**
* Constructor for the radar panel.
* Loads images, sets up the interactive panel and
* "navigation" buttons.
*/
public RadarPanel() {
btnMinus = GuiImageBroker.getButton("-", "minus");
btnToggle = GuiImageBroker.getButton("Toggle", "toggle");
btnPlus = GuiImageBroker.getButton("+", "plus");
final CellPanel buttonPanel = new CellPanel();
buttonPanel.addNext(btnMinus, CellConstants.HSTRETCH, (CellConstants.FILL | CellConstants.WEST));
buttonPanel.addNext(btnToggle, CellConstants.HSTRETCH, CellConstants.FILL);
buttonPanel.addLast(btnPlus, CellConstants.HSTRETCH, (CellConstants.FILL | CellConstants.EAST));
if (Preferences.itself().tabsAtTop) {
if (Preferences.itself().menuAtTab)
this.addLast(buttonPanel, CellConstants.HSTRETCH, CellConstants.HFILL);
} else {
if (!Preferences.itself().menuAtTab)
this.addLast(buttonPanel, CellConstants.HSTRETCH, CellConstants.HFILL);
}
this.addLast(iActP = new MyInteractivePanel(), CellConstants.STRETCH, CellConstants.FILL);
if (Preferences.itself().tabsAtTop) {
if (!Preferences.itself().menuAtTab)
this.addLast(buttonPanel, CellConstants.HSTRETCH, CellConstants.HFILL);
} else {
if (Preferences.itself().menuAtTab)
this.addLast(buttonPanel, CellConstants.HSTRETCH, CellConstants.HFILL);
}
}
/**
* Informs the radar panel on preferences and currently loaded cache
* database. It also calculates the maximum size available for drawing
* the radar.
*/
public void setParam(CacheDB db, CacheHolder sWp) {
selectedWaypoint = sWp;
cacheDB = db;
height = (Preferences.itself().getScreenHeight()) * 6 / 5; // add 10% each at top/bottom
width = (Preferences.itself().getScreenWidth()) * 6 / 5;
}
// Call this after the centre has changed to re-center the radar panel
public void recenterRadar() {
reCenterImage = true;
}
/**
* Public method to draw the different caches and the
* radar background
*/
public void drawThePanel() {
// If there are any images remove them!
final int anz = iActP.images.size();
for (int i = 0; i < anz; i++) {
iActP.removeImage((AniImage) iActP.images.get(0));
}
iActP.refresh();
drawBackground();
drawCaches();
iActP.repaintNow();
if (reCenterImage) {
// Hack to scroll to left origin for a defined position for subsequent scroll which centers the image
iActP.scroll(-1000, -1000);
final Dimension dispSize = getDisplayedSize(null);
iActP.scroll((width - dispSize.width) / 2, (height - dispSize.height + btnMinus.getSize(null).height) / 2);
reCenterImage = false;
}
}
/**
* Private method to draw the caches.
*/
private void drawCaches() {
final Font radarFont = new Font("Gui", Font.BOLD, Preferences.itself().fontSize);
final FontMetrics fm = getFontMetrics(radarFont);
AniImage aImg;
RadarPanelImage rpi;
int drX, drY = 0;
CacheHolder holder;
double degrees;
final double pi180 = java.lang.Math.PI / 180.0;
for (int i = cacheDB.size() - 1; i >= 0; i--) {
holder = cacheDB.get(i);
if (holder.isVisible() && holder.getWpt().isValid()) {
degrees = holder.degrees * pi180;
drX = new Float(holder.kilom / scale * java.lang.Math.sin(degrees)).intValue();
drY = -new Float(holder.kilom / scale * java.lang.Math.cos(degrees)).intValue();
if (centerX + drX >= 0 && centerY + drY >= 0 && centerX + drX <= width && centerY + drY <= height) {
if (toggleMod > 0) {
String s;
if (toggleMod == 1)
s = holder.getCode();
else
s = holder.getName();
if (s.length() > 0) {
int tw;
final Image img = new Image(tw = fm.getTextWidth(s), fm.getHeight());
final Graphics g = new Graphics(img);
g.setFont(radarFont);
g.setColor(Color.Black);
g.fillRect(0, 0, tw, fm.getHeight());
g.setColor(Color.White);
g.drawText(s, 0, 0);
aImg = new AniImage(img);
aImg.setLocation(centerX + drX + 5, centerY + drY);
aImg.transparentColor = Color.Black;
aImg.properties = mImage.IsNotHot;
iActP.addImage(aImg);
}
}
Image imgCache = CacheType.getBigCacheIcon(holder);
rpi = new RadarPanelImage(imgCache);
rpi.wayPoint = holder.getCode();
rpi.rownum = i;
final int dx = imgCache.getWidth();
final int dy = imgCache.getHeight();
rpi.setLocation(centerX + drX - dx / 2, centerY + drY - dy / 2);
iActP.addImage(rpi);
if (holder == selectedWaypoint) { // Draw red circle around selected wpt
final int diag = (int) (java.lang.Math.sqrt(dx * dx + dy * dy) + 0.5);
final Image imgCircle = new Image(diag, diag);
final Graphics gCircle = new Graphics(imgCircle);
gCircle.setColor(Color.Black);
gCircle.fillRect(0, 0, diag, diag);
gCircle.setColor(RED);
gCircle.drawEllipse(0, 0, diag, diag);
aImg = new AniImage(imgCircle);
aImg.setLocation(centerX + drX - diag / 2, centerY + drY - diag / 2);
aImg.transparentColor = Color.Black;
aImg.properties = mImage.IsNotHot;
iActP.addImage(aImg);
}
}// if center...
}// if is_black...
}
}
/**
* Private method to draw the black background and green radar.
* Also calculates some other parameters.
* Always call this before calling drawCaches().
*/
private void drawBackground() {
final Rect r = new Rect(new Dimension(width, height));
iActP.virtualSize = r;
iActP.refresh();
final Image img = new Image(width, height);
final Graphics g = new Graphics(img);
g.setColor(Color.Black);
g.fillRect(0, 0, width, height);
if (width < height) {
scale = (double) scaleKm / (double) height;
} else {
scale = (double) scaleKm / (double) width;
}
centerX = (width / 2);
centerY = (height / 2);
g.setColor(GREEN);
// Draw rings each 10 km
for (int i = 1; i <= scaleKm / 10; i++) {
drawRangeRing(g, (float) (i * 10));
}
// Draw 1 to 5 km rings only if we have zoomed in (useful for cities with high density of caches)
if (scaleKm <= 25) {
g.setColor(YELLOW);
drawRangeRing(g, 5.0f);
if (scaleKm <= 10) {
drawRangeRing(g, 2.0f);
drawRangeRing(g, 1.0f);
}
if (scaleKm <= 5) {
drawRangeRing(g, 0.5f);
}
}
g.drawLine(centerX, 0, centerX, height);
g.drawLine(0, centerY, width, centerY);
g.free();
final AniImage aImg = new AniImage(img);
// iActP.addImage(aImg);
iActP.backgroundImage = img;
final int xPos = (Preferences.itself().getScreenWidth() / 2 - width / 2);
aImg.setLocation(xPos, 0);
aImg.refresh();
}
public void drawRangeRing(Graphics g, float radius) {
int pixelRadius = (int) (radius / scale);
g.drawEllipse(centerX - pixelRadius, centerY - pixelRadius, pixelRadius * 2, pixelRadius * 2);
if (radius < 1.0) {
String s = Common.DoubleToString(radius * 1000, 0, 0);
g.drawText(s + " m", centerX - pixelRadius * 7 / 10, // ~ radius / sqrt(2)
centerY - pixelRadius * 7 / 10);
} else {
String s = Common.DoubleToString(radius, 0, 0);
g.drawText(s + " km", centerX - pixelRadius * 7 / 10, // ~ radius / sqrt(2)
centerY - pixelRadius * 7 / 10);
}
}
public void onEvent(Event ev) {
if (ev instanceof ControlEvent && ev.type == ControlEvent.PRESSED) {
if (ev.target == btnPlus) {
if (scaleKm > 10)
scaleKm = scaleKm - 10;
else if (scaleKm == 10)
scaleKm = 5;
else if (scaleKm == 5)
scaleKm = 2;
else
scaleKm = 1;
drawThePanel();
}
if (ev.target == btnMinus) {
if (scaleKm == 1)
scaleKm = 2;
else if (scaleKm == 2)
scaleKm = 5;
else if (scaleKm == 5)
scaleKm = 10;
else
scaleKm = scaleKm + 10;
drawThePanel();
}
if (ev.target == btnToggle) {
toggleMod++;
if (toggleMod > 2)
toggleMod = 0;
drawThePanel();
}
}
}
}
/**
* This class allows handling of a user click on a cache
* in the radar panel.
*
* @see RadarPanel
*/
class MyInteractivePanel extends InteractivePanel {
boolean penMoving = false;
int x1, y1, x2, y2 = 0;
static Color RED = new Color(255, 0, 0);
//Font font = new Font("gui", Font.BOLD,Preferences.itself().fontSize);
FontMetrics fm = null;
long timePenOn = 0;
AniImage imgInfo;
String strDifficulty = MyLocale.getMsg(1120, "Diff");
String strTerrain = MyLocale.getMsg(1121, "Terr");
AniImage imgDrag; // Allows the dragging of the cache into the cachelist
boolean canScroll = true;
{
font = new Font("gui", Font.BOLD, Preferences.itself().fontSize);
fm = getFontMetrics(font);
}
private void clearInfo() {
removeImage(imgInfo);
imgInfo = null;
refresh();
onImage = null;
}
public void imageClicked(AniImage which, Point pos) {
long timePenOff = Vm.getTimeStampLong();
// If the pen rested more than 500 msec, we only display the info and don't treat it as a click
if (timePenOff - timePenOn < 500 || !Vm.isMobile()) {
if (which instanceof RadarPanelImage) {
RadarPanelImage ich = (RadarPanelImage) which;
MainTab.itself.clearDetails();
MainTab.itself.selectAndActive(ich.rownum);
}
} else {
if (imgInfo != null)
clearInfo();
}
}
public boolean imageMovedOn(AniImage which) {
timePenOn = Vm.getTimeStampLong();
setFont(font);
RadarPanelImage imgRP = (RadarPanelImage) which;
CacheDB cacheDB = MainForm.profile.cacheDB;
CacheHolder ch = cacheDB.get(imgRP.rownum);
wayPoint = ch.getCode();
String s = wayPoint + " " + CacheSize.getExportShortId(ch.getSize()) + " / " + strDifficulty + "=" + ch.getDifficulty() + " " + strTerrain + "=" + ch.getTerrain();
String s1 = ch.getName();
if (s1.length() > 40)
s1 = s1.substring(0, 40);
int tw = fm.getTextWidth(s) + 2;
int tw1 = fm.getTextWidth(s1) + 2;
if (tw1 > tw)
tw = tw1;
int h = fm.getHeight();
Image img = new Image(tw, h + h);
Graphics g = new Graphics(img);
g.setFont(font);
g.setColor(new Color(0, 0, 255));
g.fillRect(0, 0, tw, h + h);
g.setColor(Color.White);
g.drawText(s, 1, 1);
g.drawText(s1, 1, h);
imgInfo = new AniImage(img);
Rect r = getVisibleArea(null);
imgInfo.setLocation(r.x, r.y); // Place the info at top left corner
imgInfo.properties = mImage.IsNotHot;
addImage(imgInfo);
refreshOnScreen(imgInfo);
imgDrag = which;
return true;
}
public boolean imageMovedOff(AniImage which) {
clearInfo();
return true;
}
public void onPenEvent(PenEvent ev) {
super.onPenEvent(ev);
if (ev.type == PenEvent.PEN_UP) {
clearInfo();
// The next line is needed due to a bug in EWE (it does not call penReleased)
if (isDragging)
penReleased(new Point(ev.x, ev.y));
}
}
///////////////////////////////////////////////////
// Allow the caches to be dragged into a cachelist
///////////////////////////////////////////////////
String wayPoint;
public void startDragging(DragContext dc) {
if (!MainForm.itself.cacheTourVisible)
return;
CacheHolder ch = MainForm.profile.cacheDB.get(wayPoint);
if (ch != null) {
IconAndText icnDrag = new IconAndText();
icnDrag.addColumn(CacheType.getTypeImage(ch.getType()));
icnDrag.addColumn(ch.getCode());
dc.dragData = dc.startImageDrag(icnDrag, new Point(8, 8), this);
canScroll = false;
}
}
public void stopDragging(DragContext dc) {
canScroll = true;
}
public boolean imageBeginDragged(AniImage which, Point pos) {
if (!MainForm.itself.cacheTourVisible)
return false;
canScroll = false;
clearInfo();
wayPoint = null;
AniImage dragImage = null;
if (which instanceof RadarPanelImage) {
RadarPanelImage imgRP = (RadarPanelImage) which;
CacheDB cacheDB = MainForm.profile.cacheDB;
CacheHolder ch = cacheDB.get(imgRP.rownum);
wayPoint = ch.getCode();
int tw, th;
Image img = new Image(tw = fm.getTextWidth(wayPoint + 15), th = fm.getHeight() > 15 ? fm.getHeight() : 15);
Graphics g = new Graphics(img);
g.setFont(font);
g.setColor(Color.White);
g.fillRect(0, 0, tw, th);
g.setColor(new Color(255, 0, 0));
g.drawText(wayPoint, 15, 1);
g.drawImage(which.image, 0, 0);
dragImage = new AniImage(img);
dragImage.properties |= mImage.IsMoveable;
dragImage.setLocation(pos.x, pos.y);
}
return super.imageBeginDragged(dragImage, pos);
}
public boolean imageDragged(ImageDragContext drag, Point pos) {
if (drag.image != null) {
/* Point p = Gui.getPosInParent(this,getWindow());
p.x += pos.x-origin.x;
p.y += pos.y-origin.y;
Control c = getWindow().findChild(p.x,p.y);
*/
drag.clearPendingDrags();
}
return super.imageDragged(drag, pos);
}
public boolean imageNotDragged(ImageDragContext drag, Point pos) {
if (drag.image != null) {
images.remove(drag.image);
drag.image = null;
refresh();
}
Point p = Gui.getPosInParent(this, getWindow());
p.x += drag.curPoint.x - origin.x;
p.y += drag.curPoint.y - origin.y;
Control c = getWindow().findChild(p.x, p.y);
if (c instanceof mList && c.text.equals("CacheList")) {
if (MainForm.itself.getCacheTour().addCache(wayPoint)) {
c.repaintNow();
((mList) c).makeItemVisible(((mList) c).itemsSize() - 1);
}
}
return false;
}
public boolean canScreenScroll() {
return canScroll;
}
public boolean scroll(int dx, int dy, Point moved) {
if (canScroll)
return super.scroll(dx, dy, moved);
else
return false;
}
}
/**
* The ImagePanelImage extends AniImage by a fileName.
* This is an easy way to identify the image clicked,
* what is needed to display the full image from the
* thumbnail.
*/
class RadarPanelImage extends AniImage {
public String wayPoint = "";
public int rownum;
public RadarPanelImage(mImage i) {
super(i);
}
public RadarPanelImage(Image i) {
super(i);
}
}