package tim.prune.gui.map;
+import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.FontMetrics;
import java.awt.Graphics;
+import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import tim.prune.DataSubscriber;
import tim.prune.FunctionLibrary;
import tim.prune.I18nManager;
+import tim.prune.UpdateMessageBroker;
+import tim.prune.config.ColourScheme;
+import tim.prune.config.Config;
+import tim.prune.data.Checker;
+import tim.prune.data.Coordinate;
+import tim.prune.data.DataPoint;
import tim.prune.data.DoubleRange;
+import tim.prune.data.Latitude;
+import tim.prune.data.Longitude;
import tim.prune.data.Selection;
import tim.prune.data.Track;
import tim.prune.data.TrackInfo;
private Selection _selection = null;
/** Previously selected point */
private int _prevSelectedPoint = -1;
- /** Tile cacher */
- private MapTileCacher _tileCacher = new MapTileCacher(this);
+ /** Tile manager */
+ private MapTileManager _tileManager = new MapTileManager(this);
/** Image to display */
private BufferedImage _mapImage = null;
/** Slider for transparency */
private static final int AUTOPAN_DISTANCE = 75;
// Colours
- private static final Color COLOR_BG = Color.WHITE;
private static final Color COLOR_MESSAGES = Color.GRAY;
- private static final Color COLOR_POINT = Color.BLUE;
- private static final Color COLOR_POINT_DELETED = Color.RED;
- private static final Color COLOR_CURR_RANGE = Color.GREEN;
- private static final Color COLOR_CROSSHAIRS = Color.RED;
- private static final Color COLOR_WAYPT_NAME = Color.BLACK;
- private static final Color COLOR_PHOTO_PT = Color.ORANGE;
/**
ItemListener mapCheckListener = new ItemListener() {
public void itemStateChanged(ItemEvent e)
{
- _tileCacher.clearAll();
+ _tileManager.clearMemoryCaches();
_recalculate = true;
- repaint();
+ Config.setConfigBoolean(Config.KEY_SHOW_MAP, e.getStateChange() == ItemEvent.SELECTED);
+ UpdateMessageBroker.informSubscribers(); // to let menu know
}
};
_topPanel = new JPanel();
newPointItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e)
{
- _app.createPoint(MapUtils.getLatitudeFromY(_mapPosition.getYFromPixels(_popupMenuY, getHeight())),
- MapUtils.getLongitudeFromX(_mapPosition.getXFromPixels(_popupMenuX, getWidth())));
+ double lat = MapUtils.getLatitudeFromY(_mapPosition.getYFromPixels(_popupMenuY, getHeight()));
+ double lon = MapUtils.getLongitudeFromX(_mapPosition.getXFromPixels(_popupMenuX, getWidth()));
+ _app.createPoint(new DataPoint(new Latitude(lat, Coordinate.FORMAT_NONE),
+ new Longitude(lon, Coordinate.FORMAT_NONE), null));
}});
newPointItem.setEnabled(true);
_popup.add(newPointItem);
_prevSelectedPoint = selectedPoint;
}
- // Draw the mapImage if necessary
+ // Draw the map contents if necessary
if ((_mapImage == null || _recalculate))
{
- getMapTiles();
- _scaleBar.updateScale(_mapPosition.getZoom(), _mapPosition.getCentreTileY());
+ paintMapContents();
+ _scaleBar.updateScale(_mapPosition.getZoom(), _mapPosition.getYFromPixels(0, 0));
}
// Draw the prepared image onto the panel
if (_mapImage != null) {
}
else
{
- inG.setColor(COLOR_BG);
+ inG.setColor(Config.getColourScheme().getColour(ColourScheme.IDX_BACKGROUND));
inG.fillRect(0, 0, getWidth(), getHeight());
inG.setColor(COLOR_MESSAGES);
inG.drawString(I18nManager.getText("display.nodata"), 50, getHeight()/2);
/**
- * Get the map tiles for the current zoom level and given tile parameters
+ * Paint the map tiles and the points on to the _mapImage
*/
- private void getMapTiles()
+ private void paintMapContents()
{
if (_mapImage == null || _mapImage.getWidth() != getWidth() || _mapImage.getHeight() != getHeight())
{
// Clear map
Graphics g = _mapImage.getGraphics();
- // Clear to white
- g.setColor(COLOR_BG);
+ // Clear to background
+ g.setColor(Config.getColourScheme().getColour(ColourScheme.IDX_BACKGROUND));
g.fillRect(0, 0, getWidth(), getHeight());
+ // Check whether maps are on or not
+ boolean showMap = Config.getConfigBoolean(Config.KEY_SHOW_MAP);
+ _mapCheckBox.setSelected(showMap);
+
// reset error message
- if (!_mapCheckBox.isSelected()) {_shownOsmErrorAlready = false;}
+ if (!showMap) {_shownOsmErrorAlready = false;}
+ _recalculate = false;
// Only get map tiles if selected
- if (_mapCheckBox.isSelected())
+ if (showMap)
{
// init tile cacher
- _tileCacher.centreMap(_mapPosition.getZoom(), _mapPosition.getCentreTileX(), _mapPosition.getCentreTileY());
+ _tileManager.centreMap(_mapPosition.getZoom(), _mapPosition.getCentreTileX(), _mapPosition.getCentreTileY());
boolean loadingFailed = false;
if (_mapImage == null) return;
- if (_tileCacher.isOverzoomed())
+ if (_tileManager.isOverzoomed())
{
// display overzoom message
g.setColor(COLOR_MESSAGES);
}
else
{
+ int numLayers = _tileManager.getNumLayers();
// Loop over tiles drawing each one
int[] tileIndices = _mapPosition.getTileIndices(getWidth(), getHeight());
int[] pixelOffsets = _mapPosition.getDisplayOffsets(getWidth(), getHeight());
for (int tileY = tileIndices[2]; tileY <= tileIndices[3]; tileY++)
{
int y = (tileY - tileIndices[2]) * 256 - pixelOffsets[1];
- Image image = _tileCacher.getTile(tileX, tileY);
- if (image != null) {
- g.drawImage(image, x, y, 256, 256, null);
+ // Loop over layers
+ for (int l=0; l<numLayers; l++)
+ {
+ Image image = _tileManager.getTile(l, tileX, tileY);
+ if (image != null) {
+ g.drawImage(image, x, y, 256, 256, null);
+ }
}
}
}
// Make maps brighter / fainter
- float[] scaleFactors = {1.0f, 1.05f, 1.1f, 1.2f, 1.6f, 2.0f};
- float scaleFactor = scaleFactors[_transparencySlider.getValue()];
+ final float[] scaleFactors = {1.0f, 1.05f, 1.1f, 1.2f, 1.6f, 2.2f};
+ final float scaleFactor = scaleFactors[_transparencySlider.getValue()];
if (scaleFactor > 1.0f)
{
RenderingHints hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
// free g
g.dispose();
- _recalculate = false;
// Zoom to fit if no points found
if (pointsPainted <= 0 && _checkBounds) {
zoomToFit();
}
_checkBounds = false;
// enable / disable transparency slider
- _transparencySlider.setEnabled(_mapCheckBox.isSelected());
+ _transparencySlider.setEnabled(showMap);
}
*/
private int paintPoints(Graphics inG)
{
+ // Set up colours
+ final Color pointColour = Config.getColourScheme().getColour(ColourScheme.IDX_POINT);
+ final Color rangeColour = Config.getColourScheme().getColour(ColourScheme.IDX_SELECTION);
+ final Color currentColour = Config.getColourScheme().getColour(ColourScheme.IDX_PRIMARY);
+ final Color secondColour = Config.getColourScheme().getColour(ColourScheme.IDX_SECONDARY);
+ final Color textColour = Config.getColourScheme().getColour(ColourScheme.IDX_TEXT);
+
+ // try to set double line width for painting
+ if (inG instanceof Graphics2D) {
+ ((Graphics2D) inG).setStroke(new BasicStroke(2.0f));
+ }
int pointsPainted = 0;
// draw track points
- inG.setColor(COLOR_POINT);
+ inG.setColor(pointColour);
int prevX = -1, prevY = -1;
boolean connectPoints = _connectCheckBox.isSelected();
boolean prevPointVisible = false, currPointVisible = false;
+ boolean anyWaypoints = false;
+ boolean isWaypoint = false;
for (int i=0; i<_track.getNumPoints(); i++)
{
int px = getWidth() / 2 + _mapPosition.getXFromCentre(_track.getX(i));
int py = getHeight() / 2 + _mapPosition.getYFromCentre(_track.getY(i));
currPointVisible = px >= 0 && px < getWidth() && py >= 0 && py < getHeight();
+ isWaypoint = _track.getPoint(i).isWaypoint();
+ anyWaypoints = anyWaypoints || isWaypoint;
if (currPointVisible)
{
- if (!_track.getPoint(i).isWaypoint())
+ if (!isWaypoint)
{
// Draw rectangle for track point
if (_track.getPoint(i).getDeleteFlag()) {
- inG.setColor(COLOR_POINT_DELETED);
+ inG.setColor(currentColour);
}
else {
- inG.setColor(COLOR_POINT);
+ inG.setColor(pointColour);
}
inG.drawRect(px-2, py-2, 3, 3);
pointsPainted++;
}
}
- if (!_track.getPoint(i).isWaypoint())
+ if (!isWaypoint)
{
// Connect track points if either of them are visible
if (connectPoints && (currPointVisible || prevPointVisible)
}
// Loop over points, just drawing blobs for waypoints
- inG.setColor(COLOR_WAYPT_NAME);
+ inG.setColor(textColour);
FontMetrics fm = inG.getFontMetrics();
int nameHeight = fm.getHeight();
int width = getWidth();
int height = getHeight();
- for (int i=0; i<_track.getNumPoints(); i++)
- {
- if (_track.getPoint(i).isWaypoint())
+ if (anyWaypoints) {
+ for (int i=0; i<_track.getNumPoints(); i++)
{
- int px = getWidth() / 2 + _mapPosition.getXFromCentre(_track.getX(i));
- int py = getHeight() / 2 + _mapPosition.getYFromCentre(_track.getY(i));
- if (px >= 0 && px < getWidth() && py >= 0 && py < getHeight())
+ if (_track.getPoint(i).isWaypoint())
{
- inG.fillRect(px-3, py-3, 6, 6);
- pointsPainted++;
+ int px = getWidth() / 2 + _mapPosition.getXFromCentre(_track.getX(i));
+ int py = getHeight() / 2 + _mapPosition.getYFromCentre(_track.getY(i));
+ if (px >= 0 && px < getWidth() && py >= 0 && py < getHeight())
+ {
+ inG.fillRect(px-3, py-3, 6, 6);
+ pointsPainted++;
+ }
}
}
- }
- // Loop over points again, now draw names for waypoints
- for (int i=0; i<_track.getNumPoints(); i++)
- {
- if (_track.getPoint(i).isWaypoint())
+ // Loop over points again, now draw names for waypoints
+ for (int i=0; i<_track.getNumPoints(); i++)
{
- int px = getWidth() / 2 + _mapPosition.getXFromCentre(_track.getX(i));
- int py = getHeight() / 2 + _mapPosition.getYFromCentre(_track.getY(i));
- if (px >= 0 && px < getWidth() && py >= 0 && py < getHeight())
+ if (_track.getPoint(i).isWaypoint())
{
- // Figure out where to draw waypoint name so it doesn't obscure track
- String waypointName = _track.getPoint(i).getWaypointName();
- int nameWidth = fm.stringWidth(waypointName);
- boolean drawnName = false;
- // Make arrays for coordinates right left up down
- int[] nameXs = {px + 2, px - nameWidth - 2, px - nameWidth/2, px - nameWidth/2};
- int[] nameYs = {py + (nameHeight/2), py + (nameHeight/2), py - 2, py + nameHeight + 2};
- for (int extraSpace = 4; extraSpace < 13 && !drawnName; extraSpace+=2)
+ int px = getWidth() / 2 + _mapPosition.getXFromCentre(_track.getX(i));
+ int py = getHeight() / 2 + _mapPosition.getYFromCentre(_track.getY(i));
+ if (px >= 0 && px < getWidth() && py >= 0 && py < getHeight())
{
- // Shift arrays for coordinates right left up down
- nameXs[0] += 2; nameXs[1] -= 2;
- nameYs[2] -= 2; nameYs[3] += 2;
- // Check each direction in turn right left up down
- for (int a=0; a<4; a++)
+ // Figure out where to draw waypoint name so it doesn't obscure track
+ String waypointName = _track.getPoint(i).getWaypointName();
+ int nameWidth = fm.stringWidth(waypointName);
+ boolean drawnName = false;
+ // Make arrays for coordinates right left up down
+ int[] nameXs = {px + 2, px - nameWidth - 2, px - nameWidth/2, px - nameWidth/2};
+ int[] nameYs = {py + (nameHeight/2), py + (nameHeight/2), py - 2, py + nameHeight + 2};
+ for (int extraSpace = 4; extraSpace < 13 && !drawnName; extraSpace+=2)
{
- if (nameXs[a] > 0 && (nameXs[a] + nameWidth) < width
- && nameYs[a] < height && (nameYs[a] - nameHeight) > 0
- && !overlapsPoints(nameXs[a], nameYs[a], nameWidth, nameHeight))
+ // Shift arrays for coordinates right left up down
+ nameXs[0] += 2; nameXs[1] -= 2;
+ nameYs[2] -= 2; nameYs[3] += 2;
+ // Check each direction in turn right left up down
+ for (int a=0; a<4; a++)
{
- // Found a rectangle to fit - draw name here and quit
- inG.drawString(waypointName, nameXs[a], nameYs[a]);
- drawnName = true;
- break;
+ if (nameXs[a] > 0 && (nameXs[a] + nameWidth) < width
+ && nameYs[a] < height && (nameYs[a] - nameHeight) > 0
+ && !overlapsPoints(nameXs[a], nameYs[a], nameWidth, nameHeight, textColour))
+ {
+ // Found a rectangle to fit - draw name here and quit
+ inG.drawString(waypointName, nameXs[a], nameYs[a]);
+ drawnName = true;
+ break;
+ }
}
}
}
}
}
// Loop over points, drawing blobs for photo points
- inG.setColor(COLOR_PHOTO_PT);
+ inG.setColor(secondColour);
for (int i=0; i<_track.getNumPoints(); i++)
{
if (_track.getPoint(i).getPhoto() != null)
// Draw selected range
if (_selection.hasRangeSelected())
{
- inG.setColor(COLOR_CURR_RANGE);
+ inG.setColor(rangeColour);
for (int i=_selection.getStart(); i<=_selection.getEnd(); i++)
{
int px = getWidth() / 2 + _mapPosition.getXFromCentre(_track.getX(i));
{
int px = getWidth() / 2 + _mapPosition.getXFromCentre(_track.getX(selectedPoint));
int py = getHeight() / 2 + _mapPosition.getYFromCentre(_track.getY(selectedPoint));
- inG.setColor(COLOR_CROSSHAIRS);
+ inG.setColor(currentColour);
// crosshairs
inG.drawLine(px, 0, px, getHeight());
inG.drawLine(0, py, getWidth(), py);
* @param inY bottom Y coordinate
* @param inWidth width of rectangle
* @param inHeight height of rectangle
- * @return true if there's at least one data point in the rectangle
+ * @param inTextColour colour of text
+ * @return true if the rectangle overlaps stuff too close to the given colour
*/
- private boolean overlapsPoints(int inX, int inY, int inWidth, int inHeight)
+ private boolean overlapsPoints(int inX, int inY, int inWidth, int inHeight, Color inTextColour)
{
- // each of the colour channels must be brighter than this to count as empty
- final int BRIGHTNESS_LIMIT = 210;
+ // each of the colour channels must be further away than this to count as empty
+ final int BRIGHTNESS_LIMIT = 80;
+ final int textRGB = inTextColour.getRGB();
+ final int textLow = textRGB & 255;
+ final int textMid = (textRGB >> 8) & 255;
+ final int textHigh = (textRGB >> 16) & 255;
try
{
// loop over x coordinate of rectangle
{
int pixelColor = _mapImage.getRGB(inX + x, inY - y);
// split into four components rgba
- int lowestBit = pixelColor & 255;
- int secondBit = (pixelColor >> 8) & 255;
- int thirdBit = (pixelColor >> 16) & 255;
+ int pixLow = pixelColor & 255;
+ int pixMid = (pixelColor >> 8) & 255;
+ int pixHigh = (pixelColor >> 16) & 255;
//int fourthBit = (pixelColor >> 24) & 255; // alpha ignored
- if (lowestBit < BRIGHTNESS_LIMIT || secondBit < BRIGHTNESS_LIMIT || thirdBit < BRIGHTNESS_LIMIT) return true;
+ // If colours are too close in any channel then it's an overlap
+ if (Math.abs(pixLow-textLow) < BRIGHTNESS_LIMIT ||
+ Math.abs(pixMid-textMid) < BRIGHTNESS_LIMIT ||
+ Math.abs(pixHigh-textHigh) < BRIGHTNESS_LIMIT) {return true;}
}
}
}
// select point if it's a left-click
if (!inE.isMetaDown())
{
- int pointIndex = _track.getNearestPointIndex(
- _mapPosition.getXFromPixels(inE.getX(), getWidth()),
- _mapPosition.getYFromPixels(inE.getY(), getHeight()),
- _mapPosition.getBoundsFromPixels(CLICK_SENSITIVITY), false);
- _trackInfo.selectPoint(pointIndex);
+ if (inE.getClickCount() == 1)
+ {
+ // single click
+ int pointIndex = _track.getNearestPointIndex(
+ _mapPosition.getXFromPixels(inE.getX(), getWidth()),
+ _mapPosition.getYFromPixels(inE.getY(), getHeight()),
+ _mapPosition.getBoundsFromPixels(CLICK_SENSITIVITY), false);
+ // Extend selection for shift-click
+ if (inE.isShiftDown()) {
+ _trackInfo.extendSelection(pointIndex);
+ }
+ else {
+ _trackInfo.selectPoint(pointIndex);
+ }
+ }
+ else if (inE.getClickCount() == 2) {
+ // double click
+ panMap(inE.getX() - getWidth()/2, inE.getY() - getHeight()/2);
+ zoomIn();
+ }
}
else
{
_checkBounds = true;
}
if ((inUpdateType & DataSubscriber.MAPSERVER_CHANGED) > 0) {
- _tileCacher.setTileConfig(new MapTileConfig());
+ _tileManager.resetConfig();
}
repaint();
// enable or disable components
{
int code = inE.getKeyCode();
int currPointIndex = _selection.getCurrentPointIndex();
- // Check for meta key
- if (inE.isControlDown())
+ // Check for Ctrl key (for Linux/Win) or meta key (Clover key for Mac)
+ if (inE.isControlDown() || inE.isMetaDown())
{
// Check for arrow keys to zoom in and out
if (code == KeyEvent.VK_UP)
_trackInfo.selectPoint(currPointIndex-1);
else if (code == KeyEvent.VK_RIGHT)
_trackInfo.selectPoint(currPointIndex+1);
+ else if (code == KeyEvent.VK_PAGE_UP)
+ _trackInfo.selectPoint(Checker.getPreviousSegmentStart(
+ _trackInfo.getTrack(), _trackInfo.getSelection().getCurrentPointIndex()));
+ else if (code == KeyEvent.VK_PAGE_DOWN)
+ _trackInfo.selectPoint(Checker.getNextSegmentStart(
+ _trackInfo.getTrack(), _trackInfo.getSelection().getCurrentPointIndex()));
+ // Check for home and end
+ else if (code == KeyEvent.VK_HOME)
+ _trackInfo.selectPoint(0);
+ else if (code == KeyEvent.VK_END)
+ _trackInfo.selectPoint(_trackInfo.getTrack().getNumPoints()-1);
}
else
{
else if (code == KeyEvent.VK_LEFT)
rightwardsPan = -PAN_DISTANCE;
panMap(rightwardsPan, upwardsPan);
- // Check for delete key to delete current point
- if (code == KeyEvent.VK_DELETE && currPointIndex >= 0)
- {
+ // Check for backspace key to delete current point (delete key already handled by menu)
+ if (code == KeyEvent.VK_BACK_SPACE && currPointIndex >= 0) {
_app.deleteCurrentPoint();
}
}