	public void commandAction(Command c, Displayable d) {
		updateLastUserActionTime();
		try {
			if((keyboardLocked) && (d != null)) {
				keyPressed(0);
				return;
			}
			if ((c == CMDS[PAN_LEFT25_CMD]) || (c == CMDS[PAN_RIGHT25_CMD])
					|| (c == CMDS[PAN_UP25_CMD]) || (c == CMDS[PAN_DOWN25_CMD])
					|| (c == CMDS[PAN_LEFT2_CMD]) || (c == CMDS[PAN_RIGHT2_CMD])
					|| (c == CMDS[PAN_UP2_CMD]) || (c == CMDS[PAN_DOWN2_CMD])) {
				int panX = 0; int panY = 0;
				int courseDiff = 0;
				int backLightLevelDiff = 0;
				if (c == CMDS[PAN_LEFT25_CMD]) {
					panX = -25;
				} else if (c == CMDS[PAN_RIGHT25_CMD]) {
					panX = 25;
				} else if (c == CMDS[PAN_UP25_CMD]) {
					panY = -25;
				} else if (c == CMDS[PAN_DOWN25_CMD]) {
					panY = 25;
				} else if (c == CMDS[PAN_LEFT2_CMD]) {
					if (TrackPlayer.isPlaying) {
						TrackPlayer.slower();
					} else if (manualRotationMode) {
						courseDiff=-5;
					} else {
						panX = -2;
					}
					backLightLevelDiff = -25;
				} else if (c == CMDS[PAN_RIGHT2_CMD]) {
					if (TrackPlayer.isPlaying) {
						TrackPlayer.faster();
					} else if (manualRotationMode) {
						courseDiff=5;
					} else {
						panX = 2;
					}
					backLightLevelDiff = 25;
				} else if (c == CMDS[PAN_UP2_CMD]) {
					if (route!=null && Configuration.getCfgBitState(Configuration.CFGBIT_ROUTE_BROWSING)) {
						RouteInstructions.toNextInstruction(1);
					} else {
						panY = -2;
					}
				} else if (c == CMDS[PAN_DOWN2_CMD]) {
					if (route!=null && Configuration.getCfgBitState(Configuration.CFGBIT_ROUTE_BROWSING)) {
						RouteInstructions.toNextInstruction(-1);
					} else {
						panY = 2;
					}
				}
				if (backLightLevelDiff !=0  &&  System.currentTimeMillis() < (lastBackLightOnTime + 2500)) {
					Configuration.setCfgBitState(Configuration.CFGBIT_BACKLIGHT_ON, true, false);
					lastBackLightOnTime = System.currentTimeMillis();
					Configuration.addToBackLightLevel(backLightLevelDiff);
					parent.showBackLightLevel();
				} else if (imageCollector != null) {
					if (Configuration.getCfgBitState(Configuration.CFGBIT_COMPASS_DIRECTION) && compassProducer != null) {
						if (compassDeviation == 360) {
							compassDeviation = 0;
						} else {
							compassDeviation += courseDiff;
							compassDeviation %= 360;
							if (course < 0) {
								course += 360;
							}
						}
					} else {
						if (courseDiff == 360) {
							course = 0; 
						} else {
							course += courseDiff;
							course %= 360;
							if (course < 0) {
								course += 360;
							}
						}
						if (panX != 0 || panY != 0) {
							gpsRecenter = false;
						}
					}
					imageCollector.getCurrentProjection().pan(center, panX, panY);
				}
				gpsRecenter = false;
				return;
			}
			if (c == CMDS[EXIT_CMD]) {
				if (Legend.isValid && gpx.isRecordingTrk()) {
					alert(Locale.get("trace.RecordMode"), Locale.get("trace.PleaseStopRecording") , 2500);
					return;
				}
				if (Legend.isValid) {
					pause();
				}
				parent.exit();
				return;
			}
			if (c == CMDS[START_RECORD_CMD]) {
				try {
					gpx.newTrk(false);
					alert(Locale.get("trace.GpsRecording"), Locale.get("trace.StartingToRecord"), 1250);
				} catch (RuntimeException e) {
					receiveMessage(e.getMessage());
				}
				recordingsMenu = null; 
				return;
			}
			if (c == CMDS[STOP_RECORD_CMD]) {
				gpx.saveTrk(false);
				alert(Locale.get("trace.GpsRecording"), Locale.get("trace.StoppingToRecord"), 1250);
				recordingsMenu = null; 
				return;
			}
			if (c == CMDS[MANAGE_TRACKS_CMD]) {
				if (gpx.isRecordingTrk() && !gpx.isRecordingTrkSuspended()) {
					alert(Locale.get("trace.RecordMode"), Locale.get("trace.YouNeedStopRecording") , 4000);
					return;
				}
			    GuiGpx guiGpx = new GuiGpx(this);
			    guiGpx.show();
			    return;
			}
			if (c == CMDS[REFRESH_CMD]) {
				repaint();
				return;
			}
			if (c == CMDS[CONNECT_GPS_CMD]) {
				if (locationProducer == null) {
					if (TrackPlayer.isPlaying) {
						TrackPlayer.getInstance().stop();
						alert(Locale.get("trace.Trackplayer"), Locale.get("trace.PlayingStopped"), 2500);
					}
					Thread thread = new Thread(this,"LocationProducer init");
					thread.start();
				}
				return;
			}
			if (c == CMDS[DISCONNECT_GPS_CMD]) {
				if (locationProducer != null) {
					locationProducer.close();
				}
				return;
			}
			if (c == CMDS[TOGGLE_GPS_CMD]) {
				if (isGpsConnected()) {
					commandAction(DISCONNECT_GPS_CMD);
				} else {
					commandAction(CONNECT_GPS_CMD);					
				}
				return;
			}
			if (c == CMDS[SEARCH_CMD]) {
				GuiSearch guiSearch = new GuiSearch(this);
				guiSearch.show();
				return;
			}
			if (c == CMDS[ENTER_WAYP_CMD]) {
				if (gpx.isLoadingWaypoints()) {
					showAlertLoadingWpt();
				} else {
					GuiWaypointEnter gwpe = new GuiWaypointEnter(this);
					gwpe.show();
				}
				return;
			}
			if (c == CMDS[MAN_WAYP_CMD]) {
				if (gpx.isLoadingWaypoints()) {
					showAlertLoadingWpt();
				} else {
					GuiWaypoint gwp = new GuiWaypoint(this);
					gwp.show();
				}
				return;
			}
			if (c == CMDS[MAPFEATURES_CMD]) {
				GuiMapFeatures gmf = new GuiMapFeatures(this);
				gmf.show();
				repaint();
				return;
			}
			if (c == CMDS[OVERVIEW_MAP_CMD]) {
				GuiOverviewElements ovEl = new GuiOverviewElements(this);
				ovEl.show();
				repaint();
				return;
			}
			if (c == CMDS[SEND_MESSAGE_CMD]) {
				GuiSendMessage sendMsg = new GuiSendMessage(this);
				sendMsg.show();
				repaint();
				return;
			}
			if (c == CMDS[RECORDINGS_CMD]) {
				if (recordingsMenu == null) {
					boolean hasJSR120 = Configuration.hasDeviceJSR120();
					int noElements = 3;
					noElements += 2;
					if (hasJSR120) {
						noElements++;
					}
					int idx = 0;
					String[] elements = new String[noElements];
					if (gpx.isRecordingTrk()) {
						elements[idx++] = Locale.get("trace.StopGpxTracklog");
					} else {
						elements[idx++] = Locale.get("trace.StartGpxTracklog");
					}
					elements[idx++] = Locale.get("trace.SaveWaypoint");
					elements[idx++] = Locale.get("trace.EnterWaypoint");
					elements[idx++] = Locale.get("trace.TakePictures");
					if (audioRec.isRecording()) {
						elements[idx++] = Locale.get("trace.StopAudioRecording");
					} else {
						elements[idx++] = Locale.get("trace.StartAudioRecording");
					}
					if (hasJSR120) {
						elements[idx++] = Locale.get("trace.SendSMSMapPos");
					}
					recordingsMenu = new List(Locale.get("trace.Recordings"),Choice.IMPLICIT,elements,null);
					recordingsMenu.addCommand(CMDS[OK_CMD]);
					recordingsMenu.addCommand(CMDS[BACK_CMD]);
					recordingsMenu.setSelectCommand(CMDS[OK_CMD]);
					parent.show(recordingsMenu);
					recordingsMenu.setCommandListener(this);
				}
				if (recordingsMenu != null) {
					recordingsMenu.setSelectedIndex(0, true);
					parent.show(recordingsMenu);
				}
				return;
			}
			if (c == CMDS[ROUTINGS_CMD]) {
				if (routingsMenu == null) {
					String[] elements = new String[4];
					if (routeCalc || route != null) {
						elements[0] = Locale.get("trace.StopRouting");
					} else {
						elements[0] = Locale.get("trace.CalculateRoute");
					}
					elements[1] = Locale.get("trace.SetDestination");
					elements[2] = Locale.get("trace.ShowDestination");
					elements[3] = Locale.get("trace.ClearDestination");
					routingsMenu = new List(Locale.get("trace.Routing2"), Choice.IMPLICIT, elements, null);
					routingsMenu.addCommand(CMDS[OK_CMD]);
					routingsMenu.addCommand(CMDS[BACK_CMD]);
					routingsMenu.setSelectCommand(CMDS[OK_CMD]);
					routingsMenu.setCommandListener(this);
				}
				if (routingsMenu != null) {
					routingsMenu.setSelectedIndex(0, true);
					parent.show(routingsMenu);
				}
				return;
			}
			if (c == CMDS[SAVE_WAYP_CMD]) {
				if (gpx.isLoadingWaypoints()) {
					showAlertLoadingWpt();
				} else {
					PositionMark posMark = null;
					if (gpsRecenter) {
						posMark = new PositionMark(center.radlat, center.radlon,
								(int)pos.altitude, pos.timeMillis,
								 (byte)-1,  (byte)-1,
								 (byte)-1,  (byte)-1);
					} else {
						posMark = new PositionMark(center.radlat, center.radlon,
								PositionMark.INVALID_ELEVATION,
								pos.timeMillis,  (byte)-1,
								 (byte)-1,  (byte)-1,
								 (byte)-1);
					}
						showGuiWaypointSave(posMark);
				}
				return;
			}
			if (c == CMDS[ONLINE_INFO_CMD]) {
					Position oPos = new Position(center.radlat, center.radlon,
							0.0f, 0.0f, 0.0f, 0, 0);
					GuiWebInfo gWeb = new GuiWebInfo(this, oPos, pc);
					gWeb.show();
					alert(Locale.get("trace.NoOnlineCapa"),
					      Locale.get("trace.SetAppGeneric") +
					      Locale.get("trace.PropertiesFile"),
							Alert.FOREVER);
			}
			if (c == CMDS[BACK_CMD]) {
				show();
				return;
			}
			if (c == CMDS[OK_CMD]) {
				if (d == recordingsMenu) {
					 switch (recordingsMenu.getSelectedIndex()) {
			            case 0: {
		    				if ( gpx.isRecordingTrk() ) {
		    					commandAction(STOP_RECORD_CMD);
								if (! Configuration.getCfgBitState(Configuration.CFGBIT_GPX_ASK_TRACKNAME_STOP)) {
							    	show();
								}
		    				} else {
		    					commandAction(START_RECORD_CMD);
								if (! Configuration.getCfgBitState(Configuration.CFGBIT_GPX_ASK_TRACKNAME_START)) {
							    	show();
								}
		    				}
			            	break;
			            }
			            case 1: {
			            	commandAction(SAVE_WAYP_CMD);
							break;
			            }
			            case 2: {
			            	commandAction(ENTER_WAYP_CMD);
							break;
			            }
			            case 3: {
			            	commandAction(CAMERA_CMD);
			            	break;
			            }
			            case 4: {
			            	show();
			            	commandAction(TOGGLE_AUDIO_REC);
			            	break;
			            }
			            case 5: {
			            	commandAction(SEND_MESSAGE_CMD);
			            	break;
			            }
			            case 3: {
			            	commandAction(SEND_MESSAGE_CMD);
			            	break;
			            }
					 }
				}
				if (d == routingsMenu) {
					show();
					switch (routingsMenu.getSelectedIndex()) {
					case 0: {
						if (routeCalc || route != null) {
							commandAction(ROUTING_STOP_CMD);
						} else {
							commandAction(ROUTING_START_WITH_MODE_SELECT_CMD);
						}
						break;
					}
					case 1: {
						commandAction(SET_DEST_CMD);
						break;
					}
					case 2: {
						commandAction(SHOW_DEST_CMD);
						break;
					}
					case 3: {
						commandAction(CLEAR_DEST_CMD);
						break;
					}
					}
				}
				return;
			}
			if (c == CMDS[CAMERA_CMD]) {
				try {
					Class GuiCameraClass = Class.forName("de.ueller.midlet.gps.GuiCamera");
					Object GuiCameraObject = GuiCameraClass.newInstance();
					GuiCameraInterface cam = (GuiCameraInterface)GuiCameraObject;
					cam.init(this);
					cam.show();
				} catch (ClassNotFoundException cnfe) {
					logger.exception(Locale.get("trace.YourPhoneNoCamSupport"), cnfe);
				}
				return;
			}
			if (c == CMDS[TOGGLE_AUDIO_REC]) {
				if (audioRec.isRecording()) {
					audioRec.stopRecord();
				} else {
					audioRec.startRecorder();
				}
				recordingsMenu = null; 
				return;
			}
			if (c == CMDS[ROUTING_TOGGLE_CMD]) {
				if (routeCalc || route != null) {
					commandAction(ROUTING_STOP_CMD);
				} else {
					commandAction(ROUTING_START_WITH_MODE_SELECT_CMD);
				}
				return;
			}
			if (c == CMDS[ROUTING_START_WITH_MODE_SELECT_CMD]) {
				gpsRecenter = true;
				gpsRecenterInvalid = true;
				gpsRecenterStale = true;
				GuiRoute guiRoute = new GuiRoute(this, false);
				if (Configuration.getCfgBitSavedState(Configuration.CFGBIT_DONT_ASK_FOR_ROUTING_OPTIONS)) {
					commandAction(ROUTING_START_CMD);
				} else {
					guiRoute.show();
				}
				return;
			}
			if (c == CMDS[ROUTING_START_CMD]) {
				if (! routeCalc || RouteLineProducer.isRunning()) {
					routeCalc = true;
					if (Configuration.getContinueMapWhileRouteing() != Configuration.continueMap_Always ) {
	  				   stopImageCollector();
					}
					RouteInstructions.resetOffRoute(route, center);
					RoutePositionMark routeSource = new RoutePositionMark(center.radlat, center.radlon);
					logger.info("Routing source: " + routeSource);
					routeNodes=new Vector();
					routeEngine = new Routing(tiles, this);
					routeEngine.solve(routeSource, dest);
				}
				routingsMenu = null; 
				return;
			}
			if (c == CMDS[ROUTING_STOP_CMD]) {
				NoiseMaker.stopPlayer();
				if (routeCalc) {
					if (routeEngine != null) {
						routeEngine.cancelRouting();
					}
					alert(Locale.get("trace.RouteCalculation"), Locale.get("trace.Cancelled"), 1500);
				} else {
					alert(Locale.get("trace.Routing"), Locale.get("generic.Off"), 750);
				}
				endRouting();
				routingsMenu = null; 
				synchronized (this) {
					if (imageCollector != null) {
						imageCollector.newDataReady();
					}
				}
				routingsMenu = null; 
				return;
			}
			if (c == CMDS[ZOOM_IN_CMD]) {
				scale = scale / 1.5f;
				autoZoomed = false;
				return;
			}
			if (c == CMDS[ZOOM_OUT_CMD]) {
				scale = scale * 1.5f;
				autoZoomed = false;
				return;
			}
			if (c == CMDS[MANUAL_ROTATION_MODE_CMD]) {
				manualRotationMode = !manualRotationMode;
				if (manualRotationMode) {
					if (hasPointerEvents()) {
						alert(Locale.get("trace.ManualRotation"), Locale.get("trace.ChangeCourse"), 3000);
					} else {
						alert(Locale.get("trace.ManualRotation"), Locale.get("trace.ChangeCourseWithLeftRightKeys"), 3000);
					}
				} else {
					alert(Locale.get("trace.ManualRotation"), Locale.get("generic.Off"), 750);
				}
				return;
			}
			if (c == CMDS[TOGGLE_OVERLAY_CMD]) {
				showAddons++;
				repaint();
				return;
			}
			if (c == CMDS[TOGGLE_BACKLIGHT_CMD]) {
				Configuration.setCfgBitState(Configuration.CFGBIT_BACKLIGHT_ON,
									!(Configuration.getCfgBitState(Configuration.CFGBIT_BACKLIGHT_ON)),
									false);
				lastBackLightOnTime = System.currentTimeMillis();
				parent.showBackLightLevel();
				return;
			}
			if (c == CMDS[TOGGLE_FULLSCREEN_CMD]) {
				boolean fullScreen = !Configuration.getCfgBitState(Configuration.CFGBIT_FULLSCREEN);
				Configuration.setCfgBitState(Configuration.CFGBIT_FULLSCREEN, fullScreen, false);
				setFullScreenMode(fullScreen);
				return;
			}
			if (c == CMDS[TOGGLE_MAP_PROJ_CMD]) {
				if (manualRotationMode) {
					if (Configuration.getCfgBitState(Configuration.CFGBIT_COMPASS_DIRECTION) && compassProducer != null) {
						compassDeviation = 0;
					} else {
						course = 0;
					}
					alert(Locale.get("trace.ManualRotation"), Locale.get("trace.ManualToNorth"), 750);
				} else {
					alert(Locale.get("guidiscover.MapProjection"), ProjFactory.nextProj(), 750);
				}
				synchronized (this) {
					if (imageCollector != null) {
						imageCollector.newDataReady();
					}
				}
				return;
			}
			if (c == CMDS[TOGGLE_KEY_LOCK_CMD]) {
				keyboardLocked = !keyboardLocked;
				if (keyboardLocked) {
					keyPressed(0);
				} else {
					alert(Locale.get("trace.GpsMid"), hasPointerEvents() ? Locale.get("trace.KeysAndTouchscreenUnlocked") : Locale.get("trace.KeysUnlocked"), 1000);
				}
				return;
			}
			if (c == CMDS[TOGGLE_RECORDING_CMD]) {
				if ( gpx.isRecordingTrk() ) {
					commandAction(STOP_RECORD_CMD);
				} else {
					commandAction(START_RECORD_CMD);
				}
				return;
			}
			if (c == CMDS[TOGGLE_RECORDING_SUSP_CMD]) {
				if (gpx.isRecordingTrk()) {
					if ( gpx.isRecordingTrkSuspended() ) {
						alert(Locale.get("trace.GpsRecording"), Locale.get("trace.ResumingRecording"), 1000);
						gpx.resumeTrk();
					} else {
						alert(Locale.get("trace.GpsRecording"), Locale.get("trace.SuspendingRecording"), 1000);
						gpx.suspendTrk();
					}
				}
				return;
			}
			if (c == CMDS[RECENTER_GPS_CMD]) {
				gpsRecenter = true;
				gpsRecenterInvalid = true;
				gpsRecenterStale = true;
				autoZoomed = true;
				if (pos.latitude != 0.0f) {
					receivePosition(pos);
				}
				newDataReady();
				return;
			}
			if (c == CMDS[SHOW_DEST_CMD]) {
				if (dest != null) {
					gpsRecenter = false;
					prevPositionNode = center.copy(); 
					center.setLatLonRad(dest.lat, dest.lon);						
					movedAwayFromDest = false;
					updatePosition();
				}
				else {
					alert(Locale.get("trace.ShowDestination"), Locale.get("trace.DestinationNotSpecifiedYet"), 3000);
				}
				return;
			}
			if (c == CMDS[SHOW_PREVIOUS_POSITION_CMD]) {
				if (prevPositionNode != null) {
					gpsRecenter = false;
					center.setLatLon(prevPositionNode);						
					updatePosition();
				}
			}
			if (c == CMDS[DATASCREEN_CMD]) {
				showNextDataScreen(DATASCREEN_NONE);
				return;
			}
			if (c == CMDS[ICON_MENU] && Configuration.getCfgBitState(Configuration.CFGBIT_ICONMENUS)) {
				showIconMenu();
				return;
			}
			if (c == CMDS[SETUP_CMD]) {
				new GuiDiscover(parent);
				return;
			}
			if (c == CMDS[ABOUT_CMD]) {
				new Splash(parent, GpsMid.initDone);
				return;
			}
			if (c == CMDS[CELLID_LOCATION_CMD]) {
				if (Configuration.getLocationProvider() == Configuration.LOCATIONPROVIDER_SECELL && locationProducer != null) {
						locationProducer.triggerPositionUpdate();
						newDataReady();
				} else {
					if (cellIDLocationProducer == null) {
						cellIDLocationProducer = new SECellID();
						if (cellIDLocationProducer != null && !cellIDLocationProducer.init(this)) {
							logger.info("Failed to initialise CellID location producer");
						}
					}
					if (cellIDLocationProducer != null) {
						cellIDLocationProducer.triggerPositionUpdate();
						newDataReady();
					}
				}
				return;
			}
			if (c == CMDS[MANUAL_LOCATION_CMD]) {
				Position setpos = new Position(center.radlat / MoreMath.FAC_DECTORAD,
    								center.radlon / MoreMath.FAC_DECTORAD,
								    PositionMark.INVALID_ELEVATION, 0.0f, 0.0f, 1,
							            System.currentTimeMillis(), Position.TYPE_MANUAL);
				gpsRecenter = true;
				autoZoomed = true;
				receivePosition(setpos);
				receiveStatus(LocationMsgReceiver.STATUS_MANUAL, 0);
				newDataReady();
				return;
			}
			if (! routeCalc) {
				if (c == CMDS[RETRIEVE_XML]) {
					if (Legend.enableEdits) {
						if ((pc.actualWay != null) && (getUrl(pc.actualWay.urlIdx) != null)) {
							parent.alert ("Url", "Url: " + getUrl(pc.actualWay.urlIdx), Alert.FOREVER);
						}
						if ((pc.actualWay != null) && (pc.actualWay instanceof EditableWay)) {
							EditableWay eway = (EditableWay)pc.actualWay;
							GUIosmWayDisplay guiWay = new GUIosmWayDisplay(eway, pc.actualSingleTile, this);
							guiWay.show();
							guiWay.refresh();
						}
					} else {
						parent.alert("Editing", "Editing support was not enabled in Osm2GpsMid", Alert.FOREVER);
					}
				}
				if (c == CMDS[RETRIEVE_NODE]) {
					if (Legend.enableEdits) {
						GuiOSMPOIDisplay guiNode = new GuiOSMPOIDisplay(-1, null,
								center.radlat, center.radlon, this);
						guiNode.show();
						guiNode.refresh();
					} else {
						logger.error(Locale.get("trace.EditingIsNotEnabled"));
					}
				}
				if (c == CMDS[EDIT_ADDR_CMD]) {
					if (Legend.enableEdits) {
						String streetName = "";
						if ((pc != null) && (pc.actualWay != null)) {
							streetName = getName(pc.actualWay.nameIdx);
						}
						GuiOSMAddrDisplay guiAddr = new GuiOSMAddrDisplay(-1, streetName, null,
								center.radlat, center.radlon, this);
						guiAddr.show();
					} else {
						logger.error(Locale.get("trace.EditingIsNotEnabled"));
					}
				}
				if (c == CMDS[RETRIEVE_XML] || c == CMDS[RETRIEVE_NODE] || c == CMDS[EDIT_ADDR_CMD]) {
					alert("No online capabilites",
					      Locale.get("trace.SetAppGeneric") +
					      Locale.get("trace.PropertiesFile"),
						Alert.FOREVER);
				}
				if (c == CMDS[SET_DEST_CMD]) {
					RoutePositionMark pm1 = new RoutePositionMark(center.radlat, center.radlon);
					setDestination(pm1);
					return;
				}
				if (c == CMDS[CLEAR_DEST_CMD]) {
					setDestination(null);
					return;
				}
			} else {
				alert(Locale.get("trace.Error"), Locale.get("trace.CurrentlyInRouteCalculation"), 2000);
			}
		} catch (Exception e) {
 			logger.exception(Locale.get("trace.InTraceCommandAction"), e);
		}
	}
	private void startImageCollector() throws Exception {
		logger.info("Starting ImageCollector");
		Images images = new Images();
		pc = new PaintContext(this, images);
		imageCollector = new ImageCollector(tiles, this.getWidth(), this.getHeight(), this, images);
		pc.center = center.copy();
		pc.scale = scale;
		pc.xSize = this.getWidth();
		pc.ySize = this.getHeight();
	}
	private void stopImageCollector(){
		logger.info("Stopping ImageCollector");
		cleanup();
		if (imageCollector != null ) {
			imageCollector.stop();
			imageCollector=null;
		}
		System.gc();
	}
	public void startup() throws Exception {
		namesThread = new Names();
		urlsThread = new Urls();
		new DictReader(this);
		if (Configuration.getCfgBitState(Configuration.CFGBIT_AUTO_START_GPS)) {
			Thread thread = new Thread(this, "Trace");
			thread.start();
		}
		tileReader = new QueueDataReader(this);
		dictReader = new QueueDictReader(this);
		this.gpx = new Gpx();
		this.audioRec = new AudioRecorder();
		setDict(gpx, (byte)5);
		startImageCollector();
		recreateTraceLayout();
	}
	public void shutdown() {
		if (gpx != null) {
			gpx.saveTrk(true);
		}
		logger.debug("Shutdown: stopImageCollector");
		stopImageCollector();
		if (namesThread != null) {
			logger.debug("Shutdown: namesThread");
			namesThread.stop();
			namesThread = null;
		}
		if (urlsThread != null) {
			urlsThread.stop();
			urlsThread = null;
		}
		if (dictReader != null) {
			logger.debug("Shutdown: dictReader");
			dictReader.shutdown();
			dictReader = null;
		}
		if (tileReader != null) {
			logger.debug("Shutdown: tileReader");
			tileReader.shutdown();
			tileReader = null;
		}
		if (locationProducer != null) {
			logger.debug("Shutdown: locationProducer");
			locationProducer.close();
		}
		if (TrackPlayer.isPlaying) {
			logger.debug("Shutdown: TrackPlayer");
			TrackPlayer.getInstance().stop();
		}
	}
	public void sizeChanged(int w, int h) {
		updateLastUserActionTime();
		if (imageCollector != null) {
			logger.info("Size of Canvas changed to " + w + "|" + h);
			stopImageCollector();
			try {
				startImageCollector();
				imageCollector.resume();
				imageCollector.newDataReady();
			} catch (Exception e) {
				logger.exception(Locale.get("trace.CouldNotReinitialiseImageCollector"), e);
			}
			updatePosition();
		}
		tl = new TraceLayout(0, 0, w, h);
	}
	protected void paint(Graphics g) {
		logger.debug("Drawing Map screen");
		try {
			int yc = 1;
			int la = 18;
			getPC();
			g.setColor(Legend.COLORS[Legend.COLOR_MAP_BACKGROUND]);
			g.fillRect(0, 0, this.getWidth(), this.getHeight());
			pc.g = g;
			if (imageCollector != null) {
				Node drawnCenter = imageCollector.paint(pc);
				if (route != null && ri != null && pc.lineP2 != null) {
					pc.getP().forward(drawnCenter.radlat, drawnCenter.radlon, pc.lineP2);
					int maxAllowedMapMoveOffs = Math.min(pc.xSize/2, pc.ySize/2);
					if ( Math.abs(pc.lineP2.x - pc.getP().getImageCenter().x) < maxAllowedMapMoveOffs
						 &&
						 Math.abs(pc.lineP2.y - pc.getP().getImageCenter().y) < maxAllowedMapMoveOffs
					) {
						ri.showRoute(pc, drawnCenter,imageCollector.xScreenOverscan,imageCollector.yScreenOverscan);
					}
				}
			} else {
				tl.ele[TraceLayout.WAYNAME].setText(" ");
			}
			if (dest != null) {
				float distance = ProjMath.getDistance(dest.lat, dest.lon, center.radlat, center.radlon);
				atDest = (distance < 25);
				if (atDest) {
					if (movedAwayFromDest && Configuration.getCfgBitState(Configuration.CFGBIT_SND_DESTREACHED)) {
						GpsMid.mNoiseMaker.playSound(RouteSyntax.getInstance().getDestReachedSound(), (byte) 7, (byte) 1);
					}
				} else if (!movedAwayFromDest) {
					movedAwayFromDest = true;
				}
			}
			speeding = false;
			int maxSpeed = 0;
			if (gpsRecenter && actualSpeedLimitWay != null) {
				maxSpeed = actualSpeedLimitWay.getMaxSpeed();
				if (Configuration.getCfgBitState(Configuration.CFGBIT_MAXSPEED_WINTER)
                		&& (actualSpeedLimitWay.getMaxSpeedWinter() > 0)) {
					maxSpeed = actualSpeedLimitWay.getMaxSpeedWinter();
                }
				if (maxSpeed != 0 && speed > (maxSpeed + Configuration.getSpeedTolerance()) ) {
					speeding = true;
				}
			}
			if (speeding && Configuration.getCfgBitState(Configuration.CFGBIT_SPEEDALERT_SND)) {
				if ( (System.currentTimeMillis() - lastTimeOfSpeedingSound) > 10000 ) {
					lastTimeOfSpeedingSound = System.currentTimeMillis();
					GpsMid.mNoiseMaker.immediateSound(RouteSyntax.getInstance().getSpeedLimitSound());
				}
			}
			g.setColor(Legend.COLOR_MAP_TEXT);
			switch (showAddons) {
			case 1:
				yc = showMemory(g, yc, la);
				break;
			case 2:
				yc = showConnectStatistics(g, yc, la);
				break;
			default:
				showAddons = 0;
				if (Configuration.getCfgBitState(Configuration.CFGBIT_SHOW_SCALE_BAR)) {
					if (pc != null) {
						tl.calcScaleBarWidth(pc);
						tl.ele[TraceLayout.SCALEBAR].setText(" ");
					}
				}
				setPointOfTheCompass();
			}
			showMovement(g);
			LayoutElement eSolution = tl.ele[TraceLayout.SOLUTION];
			LayoutElement eRecorded = tl.ele[TraceLayout.RECORDED_COUNT];
			if (gpx.isRecordingTrk()) {
				if (gpx.isRecordingTrkSuspended()) {
					eRecorded.setColor(Legend.COLORS[Legend.COLOR_RECORDING_SUSPENDED_TEXT]); 
				} else {
					eRecorded.setColor(Legend.COLORS[Legend.COLOR_RECORDING_ON_TEXT]); 
				}
				eRecorded.setText(gpx.getTrkPointCount() + Locale.get("trace.r"));
			}
			if (TrackPlayer.isPlaying) {
				eSolution.setText(Locale.get("trace.Replay"));
			} else {
				if (locationProducer == null && !(solution == LocationMsgReceiver.STATUS_CELLID ||
										  solution == LocationMsgReceiver.STATUS_MANUAL)) {
					eSolution.setText(Locale.get("solution.Off"));
				} else {
					eSolution.setText(solutionStr);
				}
			}
			LayoutElement e = tl.ele[TraceLayout.CELLID];
			if (SECellLocLogger.isCellIDLogging() > 0) {
				if (SECellLocLogger.isCellIDLogging() == 2) {
					e.setColor(Legend.COLORS[Legend.COLOR_CELLID_LOG_ON_TEXT]); 
				} else {
					e.setColor(Legend.COLORS[Legend.COLOR_CELLID_LOG_ON_ATTEMPTING_TEXT]); 
				}
				e.setText(Locale.get("trace.cellIDs"));
			}
			e = tl.ele[TraceLayout.AUDIOREC];
			if (audioRec.isRecording()) {
				e.setColor(Legend.COLORS[Legend.COLOR_AUDIOREC_TEXT]); 
				e.setText(Locale.get("trace.AudioRec"));
			}
			if (pc != null) {
				showDestination(pc);
			}
			if (speed > 0 &&
					Configuration.getCfgBitState(Configuration.CFGBIT_SHOW_SPEED_IN_MAP)) {
				if (Configuration.getCfgBitState(Configuration.CFGBIT_METRIC)) {
					tl.ele[TraceLayout.SPEED_CURRENT].setText(" " + Integer.toString(speed) + Locale.get("trace.kmh"));
				} else {
					tl.ele[TraceLayout.SPEED_CURRENT].setText(" " + Integer.toString((int)(speed / 1.609344f)) + " mph");
				}
			}
			if (Configuration.getCfgBitState(Configuration.CFGBIT_SHOW_ALTITUDE_IN_MAP)
				&& locationProducer != null
				&& LocationMsgReceiverList.isPosValid(solution)
			) {
				tl.ele[TraceLayout.ALTITUDE].setText(showDistance(altitude, DISTANCE_ALTITUDE));
			}
			if (dest != null && (route == null || (!RouteLineProducer.isRouteLineProduced() && !RouteLineProducer.isRunning()) ) 
				&& Configuration.getCfgBitState(Configuration.CFGBIT_SHOW_AIR_DISTANCE_IN_MAP)) {
				e = Trace.tl.ele[TraceLayout.ROUTE_DISTANCE];
				e.setBackgroundColor(Legend.COLORS[Legend.COLOR_RI_DISTANCE_BACKGROUND]);
				double distLine = ProjMath.getDistance(center.radlat, center.radlon, dest.lat, dest.lon);
				e.setText(Locale.get("trace.Air") + showDistance((int) distLine, DISTANCE_AIR));
			}
			if (Configuration.getCfgBitState(Configuration.CFGBIT_SHOW_CLOCK_IN_MAP)) {
				e = tl.ele[TraceLayout.CURRENT_TIME]; 
				e.setText(DateTimeTools.getClock(System.currentTimeMillis(), true));
				tl.ele[TraceLayout.ROUTE_OFFROUTE].setVRelative(e);
			}
			setSpeedingSign(maxSpeed);
			if (hasPointerEvents()) {
				tl.ele[TraceLayout.ZOOM_IN].setText("+");
				tl.ele[TraceLayout.ZOOM_OUT].setText("-");
				tl.ele[TraceLayout.RECENTER_GPS].setText("|");
				e = tl.ele[TraceLayout.SHOW_DEST];
				if (atDest && prevPositionNode != null) {
					e.setText("<");
					e.setActionID(SHOW_PREVIOUS_POSITION_CMD);
				} else {
					e.setText(">");
					e.setActionID(SHOW_DEST_CMD + (Trace.SET_DEST_CMD << 16) );					
				}
			}
			e = tl.ele[TraceLayout.TITLEBAR];
			if (currentTitleMsgOpenCount != 0) {
				e.setText(currentTitleMsg);
				synchronized (this) {
					if (setTitleMsgTimeout != 0) {
						TimerTask timerT = new TimerTask() {
							public synchronized void run() {
								currentTitleMsgOpenCount--;
								lastTitleMsg = currentTitleMsg;
								if (currentTitleMsgOpenCount == 0) {
									logger.debug("clearing title");
									repaint();
								}
							}
						};
						GpsMid.getTimer().schedule(timerT, setTitleMsgTimeout);
						setTitleMsgTimeout = 0;
					}
				}
			}
			tl.paint(g);
			if (currentAlertsOpenCount > 0) {
				showCurrentAlert(g);
			}
		} catch (Exception e) {
			logger.silentexception("Unhandled exception in the paint code", e);
		}
	}
	public void showGuiWaypointSave(PositionMark posMark) {
    	if (guiWaypointSave == null) {
    		guiWaypointSave = new GuiWaypointSave(this);
    	}
    	if (guiWaypointSave != null) {
    		guiWaypointSave.setData(posMark);
    		guiWaypointSave.show();
    	}
	}
	private void showAlertLoadingWpt() {
		alert("Way points", "Way points are not ready yet.", 3000);
	}
	private void showCurrentAlert(Graphics g) {
		Font font = g.getFont();
		Font titleFont = Font.getFont(font.getFace(), Font.STYLE_BOLD, font.getSize());
		int fontHeight = font.getHeight();
		int y = titleFont.getHeight() + 2 + fontHeight;
		int extraWidth = font.charWidth('W');
		int alertWidth = titleFont.stringWidth(currentAlertTitle);
		int maxWidth = getWidth() - extraWidth;
		for (int i = 0; i <= 1; i++) {
			int nextSpaceAt = 0;
			int prevSpaceAt = 0;
			int start = 0;
			do {
				int width = 0;
				while (width < maxWidth && nextSpaceAt <= currentAlertMessage.length() ) {
					prevSpaceAt = nextSpaceAt;
					nextSpaceAt = currentAlertMessage.indexOf(' ', nextSpaceAt);
					if (nextSpaceAt == -1) {
						nextSpaceAt = currentAlertMessage.length();
					}
					width = font.substringWidth(currentAlertMessage, start, nextSpaceAt - start);
					nextSpaceAt++;
				}
				nextSpaceAt--;
				while (width > maxWidth) {
					if (prevSpaceAt > start && nextSpaceAt > prevSpaceAt) {
						nextSpaceAt = prevSpaceAt;
					} else {
						nextSpaceAt--;
					}
					width = font.substringWidth(currentAlertMessage, start, nextSpaceAt - start);
				}
				if (alertWidth < width ) {
					alertWidth = width;
				}
				if (i==1) {
					g.drawSubstring(currentAlertMessage, start, nextSpaceAt - start,
							getWidth()/2, y, Graphics.TOP|Graphics.HCENTER);
				}
				y += fontHeight;
				start = nextSpaceAt;
			} while (nextSpaceAt < currentAlertMessage.length() );
			if (i == 0) {
				alertWidth += extraWidth;
				int alertHeight = y;
				int alertTop = (getHeight() - alertHeight) /2;
				int alertLeft = (getWidth() - alertWidth) / 2;
				g.setColor(Legend.COLORS[Legend.COLOR_ALERT_BACKGROUND]);
				g.fillRect(alertLeft, alertTop, alertWidth, alertHeight);
				g.setColor(Legend.COLORS[Legend.COLOR_ALERT_TITLE_BACKGROUND]);
				g.fillRect(alertLeft, alertTop, alertWidth, fontHeight + 3);
				g.setColor(Legend.COLORS[Legend.COLOR_ALERT_BORDER]);
				g.setStrokeStyle(Graphics.SOLID);
				g.drawRect(alertLeft, alertTop, alertWidth, fontHeight + 3); 
				g.drawRect(alertLeft, alertTop, alertWidth, alertHeight); 
				y = alertTop + 2; 
				g.setFont(titleFont);
				g.setColor(Legend.COLORS[Legend.COLOR_ALERT_TEXT]);
				g.drawString(currentAlertTitle, getWidth()/2, y , Graphics.TOP|Graphics.HCENTER);
				g.setFont(font);
				y += (fontHeight * 3 / 2);
			}
		} 
		synchronized (this) {
			if (setAlertTimeout != 0) {
				TimerTask timerT = new TimerTask() {
					public synchronized void run() {
						currentAlertsOpenCount--;
						if (currentAlertsOpenCount == 0) {
							logger.debug("clearing alert");
							repaint();
						}
					}
				};
				GpsMid.getTimer().schedule(timerT, setAlertTimeout);
				setAlertTimeout = 0;
			}
		}
	}
	private void setSpeedingSign(int maxSpeed) {
		if (Configuration.getCfgBitState(Configuration.CFGBIT_SPEEDALERT_VISUAL)
			&&
			(
				speeding
				||
				(System.currentTimeMillis() - startTimeOfSpeedingSign) < 3000
			)
		) {
			if (speeding) {
				speedingSpeedLimit = maxSpeed;
				startTimeOfSpeedingSign = System.currentTimeMillis();
			}
			if (Configuration.getCfgBitState(Configuration.CFGBIT_METRIC)) {
				tl.ele[TraceLayout.SPEEDING_SIGN].setText(Integer.toString(speedingSpeedLimit));
			} else {
				tl.ele[TraceLayout.SPEEDING_SIGN].setText(Integer.toString((int)(speedingSpeedLimit / 1.609344f)));
			}
		} else {
			startTimeOfSpeedingSign = 0;
		}
	}
	private void getPC() {
			pc.course = course;
			pc.scale = scale;
			pc.center = center.copy();
			pc.dest = dest;
	}
	public void cleanup() {
		namesThread.cleanup();
		urlsThread.cleanup();
		tileReader.incUnusedCounter();
		dictReader.incUnusedCounter();
	}
	public void searchNextRoutableWay(RoutePositionMark pm) throws Exception {
		PaintContext pc = new PaintContext(this, null);
		pc.squareDstToActualRoutableWay = Float.MAX_VALUE;
		pc.xSize = 100;
		pc.ySize = 100;
		Projection p;
		do {
			p = new Proj2D(new Node(pm.lat,pm.lon, true),5000,pc.xSize,pc.ySize);
			pc.setP(p);
			for (int i=0; i<4; i++) {
				tiles[i].walk(pc, Tile.OPT_WAIT_FOR_LOAD | Tile.OPT_FIND_CURRENT);
			}
			if (pc.actualRoutableWay != null) {
				break;
			}
			pc.xSize += 100;
			pc.ySize += 100;
		} while(MoreMath.dist(p.getMinLat(), p.getMinLon(), p.getMinLat(), p.getMaxLon()) < 500); 
		Way w = pc.actualRoutableWay;
		pm.setEntity(w, pc.currentPos.nodeLat, pc.currentPos.nodeLon);
	}
	private void setPointOfTheCompass() {
		String c = "";
		if (ProjFactory.getProj() != ProjFactory.NORTH_UP
				&& Configuration.getCfgBitState(Configuration.CFGBIT_SHOW_POINT_OF_COMPASS)) {
			c = Configuration.getCompassDirection(course);
		}
		if (tl.bigOnScreenButtons && c.length() <= 1) {
			if (ProjFactory.getProj() == ProjFactory.NORTH_UP) {
				c = "(" + Configuration.getCompassDirection(0) + ")";
			} else {
				c = " " + c + " ";
			}
		}
		tl.ele[TraceLayout.POINT_OF_COMPASS].setText(c);
	}
	private int showConnectStatistics(Graphics g, int yc, int la) {
		g.setColor(Legend.COLORS[Legend.COLOR_MAP_TEXT]);
		GSMCell cell = null;
		if (cellIDLocationProducer != null || Configuration.getLocationProvider() == Configuration.LOCATIONPROVIDER_SECELL) {
			cell = CellIdProvider.getInstance().obtainCachedCellID();
		}
		Compass compass = null;
		if (Configuration.getCfgBitState(Configuration.CFGBIT_COMPASS_DIRECTION)) {
			compass = CompassProvider.getInstance().obtainCachedCompass();
		}
		if (cell == null) {
			g.drawString("No Cell ID available", 0, yc, Graphics.TOP
					| Graphics.LEFT);
			yc += la;
		} else {
			g.drawString("Cell: MCC=" + cell.mcc + " MNC=" + cell.mnc, 0, yc, Graphics.TOP
					| Graphics.LEFT);
			yc += la;
			g.drawString("LAC=" + cell.lac, 0, yc, Graphics.TOP
					| Graphics.LEFT);
			yc += la;
			g.drawString("cellID=" + cell.cellID, 0, yc, Graphics.TOP
					| Graphics.LEFT);
			yc += la;
		}
		if (compass == null) {
			g.drawString("No compass direction available", 0, yc, Graphics.TOP
					| Graphics.LEFT);
			yc += la;
		} else {
			g.drawString("Compass direction: " + compass.direction, 0, yc, Graphics.TOP
					| Graphics.LEFT);
			yc += la;
		}
		if (statRecord == null) {
			g.drawString("No stats yet", 0, yc, Graphics.TOP
					| Graphics.LEFT);
			return yc+la;
		}
		for (byte i = 0; i < LocationMsgReceiver.SIRF_FAIL_COUNT; i++) {
			g.drawString(statMsg[i] + statRecord[i], 0, yc, Graphics.TOP
					| Graphics.LEFT);
			yc += la;
		}
		g.drawString("BtQual : " + btquality, 0, yc, Graphics.TOP | Graphics.LEFT);
		yc += la;
		g.drawString("Count : " + collected, 0, yc, Graphics.TOP
				| Graphics.LEFT);
		yc += la;
		return yc;
	}
	public void showDestination(PaintContext pc) {
		try {
		if (dest != null) {
			pc.getP().forward(dest.lat, dest.lon, pc.lineP2);
			int x = pc.lineP2.x - imageCollector.xScreenOverscan;
			int y = pc.lineP2.y - imageCollector.yScreenOverscan;
			pc.g.drawImage(pc.images.IMG_DEST, x, y, CENTERPOS);
			pc.g.setColor(Legend.COLORS[Legend.COLOR_DEST_TEXT]);
			if (dest.displayName != null) {
				pc.g.drawString(dest.displayName, x, y+8,
					Graphics.TOP | Graphics.HCENTER);
			}
			pc.g.setColor(Legend.COLORS[Legend.COLOR_DEST_LINE]);
			pc.g.setStrokeStyle(Graphics.SOLID);
			pc.g.drawLine(x, y, pc.getP().getImageCenter().x - imageCollector.xScreenOverscan, 
					pc.getP().getImageCenter().y - imageCollector.yScreenOverscan);
		}
		} catch (Exception e) {
			if (imageCollector == null) {
				logger.silentexception("No ImageCollector", e);
			}
			e.printStackTrace();
		}
	}
	public void showMovement(Graphics g) {
		IntPoint centerP = null;
		try {
			if (imageCollector != null) {
			g.setColor(Legend.COLORS[Legend.COLOR_MAP_CURSOR]);
			centerP = pc.getP().getImageCenter();
			int centerX = centerP.x - imageCollector.xScreenOverscan;
			int centerY = centerP.y - imageCollector.yScreenOverscan;
			int posX, posY;
			if (!gpsRecenter) {
				IntPoint p1 = new IntPoint(0, 0);
				pc.getP().forward((pos.latitude * MoreMath.FAC_DECTORAD),
								  (pos.longitude * MoreMath.FAC_DECTORAD), p1);
				posX = p1.getX()-imageCollector.xScreenOverscan;
				posY = p1.getY()-imageCollector.yScreenOverscan;
			} else {
				posX = centerX;
				posY = centerY;
			}
			g.setColor(Legend.COLORS[Legend.COLOR_MAP_POSINDICATOR]);
			float radc = course * MoreMath.FAC_DECTORAD;
			int px = posX + (int) (Math.sin(radc) * 20);
			int py = posY - (int) (Math.cos(radc) * 20);
			if (!gpsRecenter || gpsRecenterInvalid) {
				g.drawLine(centerX, centerY - 12, centerX, centerY + 12);
				g.drawLine(centerX - 12, centerY, centerX + 12, centerY);
				g.drawArc(centerX - 5, centerY - 5, 10, 10, 0, 360);
			}
			if (! gpsRecenterInvalid) {
				pc.g.drawImage(gpsRecenterStale ? pc.images.IMG_POS_BG_STALE : pc.images.IMG_POS_BG, posX, posY, CENTERPOS);
				g.drawRect(posX - 4, posY - 4, 8, 8);
				g.drawLine(posX, posY, px, py);
			}
			}
		} catch (Exception e) {
			if (imageCollector == null) {
				logger.silentexception("No ImageCollector", e);
			}
			if (centerP == null) {
				logger.silentexception("No centerP", e);
			}
			e.printStackTrace();
		}
	}
	public void showNextDataScreen(int currentScreen) {
		switch (currentScreen)
		{
			case DATASCREEN_TACHO:
				if (guiTrip == null) {
					guiTrip = new GuiTrip(this);
				}
				if (guiTrip != null) {
					guiTrip.show();
				}
				break;
			case DATASCREEN_TRIP:
				if (guiSatellites == null) {
					guiSatellites = new GuiSatellites(this, locationProducer);
				}
				if (guiSatellites != null) {
					guiSatellites.show();
				}
				break;
			case DATASCREEN_SATS:
				this.show();
				break;
			case DATASCREEN_NONE:
			default:
				if (guiTacho == null) {
					guiTacho = new GuiTacho(this);
				}
				if (guiTacho != null) {
					guiTacho.show();
				}
				break;
		}
	}
	public int showMemory(Graphics g, int yc, int la) {
		g.setColor(0);
		g.drawString(Locale.get("trace.Freemem") + runtime.freeMemory(), 0, yc, Graphics.TOP
				| Graphics.LEFT);
		yc += la;
		g.drawString(Locale.get("trace.Totmem") + runtime.totalMemory(), 0, yc, Graphics.TOP
				| Graphics.LEFT);
		yc += la;
		g.drawString(Locale.get("trace.Percent")
				+ (100f * runtime.freeMemory() / runtime.totalMemory()), 0, yc,
				Graphics.TOP | Graphics.LEFT);
		yc += la;
		g.drawString(Locale.get("trace.ThreadsRunning")
				+ Thread.activeCount(), 0, yc,
				Graphics.TOP | Graphics.LEFT);
		yc += la;
		g.drawString(Locale.get("trace.Names") + namesThread.getNameCount(), 0, yc,
				Graphics.TOP | Graphics.LEFT);
		yc += la;
		g.drawString(Locale.get("trace.SingleT") + tileReader.getLivingTilesCount() + "/"
				+ tileReader.getRequestQueueSize(), 0, yc, Graphics.TOP
				| Graphics.LEFT);
		yc += la;
		g.drawString(Locale.get("trace.FileT") + dictReader.getLivingTilesCount() + "/"
				+ dictReader.getRequestQueueSize() + " Map: " + ImageCollector.icDuration + " ms", 0, yc, Graphics.TOP
				| Graphics.LEFT);
		yc += la;
		g.drawString(Locale.get("trace.LastMsg") + lastTitleMsg, 0, yc, Graphics.TOP
				| Graphics.LEFT);
		yc += la;
		g.drawString( Locale.get("trace.at") + lastTitleMsgClock, 0, yc,
				Graphics.TOP | Graphics.LEFT );
		return (yc);
	}
	private void updatePosition() {
		if (pc != null) {
			pc.center = center.copy();
			pc.scale = scale;
			pc.course=course;
			repaint();
			if (locationUpdateListeners != null && !TrackPlayer.isPlaying) {
				synchronized (locationUpdateListeners) {
					for (int i = 0; i < locationUpdateListeners.size(); i++) {
						((LocationUpdateListener)locationUpdateListeners.elementAt(i)).loctionUpdated();
					}
				}
			}
		}
	}
	public synchronized void receivePosition(float lat, float lon, float scale) {
		logger.debug("Now displaying: " + (lat * MoreMath.FAC_RADTODEC) + " | " +
			     (lon * MoreMath.FAC_RADTODEC));
		gpsRecenter = false;
		center.setLatLonRad(lat, lon);
		this.scale = scale;
		updatePosition();
	}
	public synchronized void receiveCompassStatus(int status) {
	}
	public synchronized void receiveCompass(float direction) {
		logger.debug("Got compass reading: " + direction);
		compassDirection = (int) direction;
		compassDeviated = ((int) direction + compassDeviation + 360) % 360;
		if (Configuration.getCfgBitState(Configuration.CFGBIT_COMPASS_DIRECTION) && compassProducer != null && gpsRecenter) {
			updateCourse(compassDeviated);
		}
	}
	public static void updateLastUserActionTime() {
		lastUserActionTime = System.currentTimeMillis();
	}
	public static long getDurationSinceLastUserActionTime() {
		return System.currentTimeMillis() - lastUserActionTime;
	}
	public void updateCourse(int newcourse) {
		coursegps = newcourse;
		if ((newcourse - course)> 180) {
			course = course + 360;
		}
		if ((course-newcourse)> 180) {
			newcourse = newcourse + 360;
		}
		if (Configuration.getCfgBitState(Configuration.CFGBIT_COMPASS_DIRECTION) && compassProducer != null) {
			course = newcourse;
		} else {
			course = course + ((newcourse - course)*1)/4 + 360;
		}
		while (course > 360) {
			course -= 360;
		}
	}
	public synchronized void receivePosition(Position pos) {
		logger.info("New position: " + pos);
		collected++;
		if (Configuration.getAutoRecenterToGpsMilliSecs() !=0 &&
			getDurationSinceLastUserActionTime() > Configuration.getAutoRecenterToGpsMilliSecs()
			&& isShown()
		) {
			gpsRecenter = true;
		}
		if (Configuration.getLocationProvider() == Configuration.LOCATIONPROVIDER_JSR179) {
			if (pos.type == Position.TYPE_GPS_LASTKNOWN) {
				if (this.pos.type == Position.TYPE_CELLID) {
					return;
				}
				gpsRecenterInvalid = false;
				gpsRecenterStale = true;
			}
		}
		this.pos = pos;
		if (pos.type == Position.TYPE_GPS || pos.type == Position.TYPE_CELLID || pos.type == Position.TYPE_MANUAL) {
			gpsRecenterInvalid = false;
			gpsRecenterStale = false;
		}
		if (gpsRecenter) {
			center.setLatLonDeg(pos.latitude, pos.longitude);
			speed = (int) (pos.speed * 3.6f);
			fspeed = pos.speed * 3.6f;
			if (Configuration.getCfgBitState(Configuration.CFGBIT_COMPASS_DIRECTION) && compassProducer != null) {
				updateCourse(compassDeviated);
                       } else if (fspeed >= courseMinSpeed && pos.course != Float.NaN ) {
                               if (thirdPrevCourse != -1) {
                                       updateCourse((int) pos.course);
                               } else if (prevCourse == -1) {
                                       prevCourse = (int) pos.course;
                               } else if (secondPrevCourse == -1) {
                                       if (Math.abs(prevCourse - (int)pos.course) < 30 || Math.abs(prevCourse - (int)pos.course) > 330) {
                                               secondPrevCourse = prevCourse;
                                       }
                                       prevCourse = (int) pos.course;
                               } else {
                                       if (Math.abs(prevCourse - (int)pos.course) < 30 || Math.abs(prevCourse - (int)pos.course) > 330) {
                                               thirdPrevCourse = secondPrevCourse;
                                               updateCourse((int) pos.course);
                                       } else {
                                               prevCourse = (int) pos.course;
                                               secondPrevCourse = -1;
                                       }
                               }
                       } else {
                               prevCourse = -1;
                               secondPrevCourse = -1;
                               thirdPrevCourse = -1;
			}
		}
		if (gpx.isRecordingTrk()) {
			try {
				if ((Configuration.getLocationProvider() == Configuration.LOCATIONPROVIDER_JSR179
				     && pos.type == Position.TYPE_CELLID) || pos.type == Position.TYPE_GPS_LASTKNOWN) {
				} else {
					gpx.addTrkPt(pos);
				}
			} catch (Exception e) {
				receiveMessage(e.getMessage());
			}
		}
		altitude = (int) (pos.altitude);
		if (Configuration.getCfgBitState(Configuration.CFGBIT_AUTOZOOM)
				&& gpsRecenter
				&& (isGpsConnected() || TrackPlayer.isPlaying)
				&& autoZoomed
				&& pc.getP() != null
				&& pos.speed != Float.NaN 
		) {
			final float minimumScale = Configuration.getRealBaseScale() / 1.5f;
			final int minimumSpeed = 20;
			final float maximumScale = Configuration.getRealBaseScale() * 1.5f;
			final int maximumSpeed = 160;
			int speedForScale = speed;
			float newScale = minimumScale + (maximumScale - minimumScale) * (speedForScale - minimumSpeed) / (maximumSpeed - minimumSpeed);
			if (newScale < minimumScale) {
				newScale = minimumScale;
			} else if (newScale > maximumScale) {
				newScale = maximumScale;
			}
			scale = newScale;
		}
		updatePosition();
	}
	public synchronized Position getCurrentPosition() {
		return this.pos;
	}
	public synchronized void receiveMessage(String s) {
		logger.info("Setting title: " + s);
		currentTitleMsg = s;
		synchronized (this) {
			if (setTitleMsgTimeout == 0) {
				currentTitleMsgOpenCount++;
			}
			setTitleMsgTimeout = 3000;
		}
		lastTitleMsgClock = DateTimeTools.getClock(System.currentTimeMillis(), false);
		repaint();
	}
	public void receiveSatellites(Satellite[] sats) {
	}
	public synchronized void alert(String title, String message, int timeout) {
		logger.info("Showing trace alert: " + title + ": " + message);
		if (timeout == Alert.FOREVER) {
			timeout = 10000;
		}
		currentAlertTitle = title;
		currentAlertMessage = message;
		synchronized (this) {
			if (setAlertTimeout == 0) {
				currentAlertsOpenCount++;
			}
			setAlertTimeout = timeout;
		}
		repaint();
	}
	public MIDlet getParent() {
		return parent;
	}
	protected void pointerPressed(int x, int y) {
		updateLastUserActionTime();
		long currTime = System.currentTimeMillis();
		pointerDragged = false;
		pointerDraggedMuch = false;
		pointerActionDone = false;
		centerPointerPressedN = center.copy();
		if (imageCollector != null) {
			panProjection=imageCollector.getCurrentProjection();
			pickPointStart=panProjection.inverse(x,y, pickPointStart);
		} else {
			panProjection = null;
		}
		int touchedElementId = tl.getElementIdAtPointer(x, y);
		if (touchedElementId >= 0 && (!keyboardLocked || tl.getActionIdAtPointer(x, y) == Trace.ICON_MENU)
				&&
			tl.isAnyActionIdAtPointer(x, y)
		) {
			tl.setTouchedElement((LayoutElement) tl.elementAt(touchedElementId));
			repaint();
		}
		if (!keyboardLocked && currTime - pressedPointerTime < DOUBLETAP_MAXDELAY) {
			doubleTap(x, y);
			return;
		}
		pressedPointerTime = currTime;
		Trace.touchX = x;
		Trace.touchY = y;
		if (keyboardLocked) {
			keyPressed(0);
			return;
		}		
		longTapTimerTask = new TimerTask() {
			public void run() {
				if (!pointerActionDone) {
					if (System.currentTimeMillis() - pressedPointerTime >= LONGTAP_DELAY){
						longTap();
					}
				}
			}
		};
		try {
			GpsMid.getTimer().schedule(longTapTimerTask, LONGTAP_DELAY);
		} catch (Exception e) {
			logger.error(Locale.get("trace.NoLongTapTimerTask") + e.toString());
		}
	}
	protected void pointerReleased(int x, int y) {	
		if (longTapTimerTask != null) {
			longTapTimerTask.cancel();
		}
		if (tl.getTouchedElement() != null) {
			tl.clearTouchedElement();
			repaint();
		}
		if (!pointerActionDone && !keyboardLocked) {
			touchReleaseX = x;
			touchReleaseY = y;
			singleTapTimerTask = new TimerTask() {
				public void run() {
					if (!keyboardLocked) {
						singleTap(touchReleaseX, touchReleaseY);
					}
				}
			};
			try {
				GpsMid.getTimer().schedule(singleTapTimerTask, DOUBLETAP_MAXDELAY);
			} catch (Exception e) {
				logger.error(Locale.get("trace.NoSingleTapTimerTask") + e.toString());
			}
			if (pointerDragged) {
				pointerDragged(x , y);
				return;
			}
		}
	}
	protected void pointerDragged (int x, int y) {
		updateLastUserActionTime();
		LayoutElement e = tl.getElementAtPointer(x, y);
		if (tl.getTouchedElement() != e) {
			if (longTapTimerTask != null) {
				longTapTimerTask.cancel();
			}			
			tl.clearTouchedElement();
			repaint();
		}
		if (tl.getElementAtPointer(touchX, touchY) == e && tl.isAnyActionIdAtPointer(x, y)) {
			tl.setTouchedElement(e);
			repaint();
		}
		if (pointerActionDone) {
			return;
		}		
		if (Math.abs(x - Trace.touchX) > 8
				|| 
			Math.abs(y - Trace.touchY) > 8
		) {
			pointerDraggedMuch = true;
			pressedPointerTime = 0; 
		}
		if (tl.getActionIdAtPointer(touchX, touchY) == Trace.ICON_MENU) {
			if ( tl.getActionIdAtPointer(x, y) ==  Trace.ICON_MENU
					&&
				x - touchX > getWidth() / 4
			) {
				commandAction(TOGGLE_KEY_LOCK_CMD);
				pointerActionDone = true;
			}
			return;
		}
		if (keyboardLocked) {
			return;
		}
		pointerDragged = true;
		if (!pointerDraggedMuch && tl.getElementIdAtPointer(touchX, touchY) >= 0) {
			return;
		}
		if (tl.getElementIdAtPointer(touchX, touchY) < 0 && imageCollector != null && panProjection != null) {
			pickPointEnd=panProjection.inverse(x,y, pickPointEnd);
			center.radlat=centerPointerPressedN.radlat-(pickPointEnd.radlat-pickPointStart.radlat);
			center.radlon=centerPointerPressedN.radlon-(pickPointEnd.radlon-pickPointStart.radlon);
			imageCollector.newDataReady();
			gpsRecenter = false;
		}
	}
	private void singleTap(int x, int y) {
		pointerActionDone = true;
		if (tl.getElementIdAtPointer(touchX, touchY) < 0) {							
			if (Configuration.getCfgBitState(Configuration.CFGBIT_MAPTAP_SINGLE)) {
				if (pointerDraggedMuch) {
					return;
				}
				logger.debug("single tap map");
				if (!tl.bigOnScreenButtons) {
					tl.setOnScreenButtonSize(true);
					final long BIGBUTTON_DURATION = 5000;
					bigButtonTimerTask = new TimerTask() {
						public void run() {
							if (System.currentTimeMillis() - lastUserActionTime > BIGBUTTON_DURATION ) {
								tl.setOnScreenButtonSize(false);
								requestRedraw();						
								bigButtonTimerTask.cancel();
							}
						}
					};
					try {
						GpsMid.getTimer().schedule(bigButtonTimerTask, BIGBUTTON_DURATION, 500);
					} catch (Exception e) {
						logger.error("Error scheduling bigButtonTimerTask: " + e.toString());
					}
				}
			}
			repaint();
		} else if (tl.getElementIdAtPointer(x, y) == tl.getElementIdAtPointer(touchX, touchY)) {
			tl.clearTouchedElement();
			int actionId = tl.getActionIdAtPointer(x, y);
			if (actionId > 0) {
				logger.debug("single tap button: " + actionId + " x: " + touchX + " y: " + touchY);
				if (System.currentTimeMillis() < (lastBackLightOnTime + 2500)) {
					if (actionId == ZOOM_IN_CMD) {
						actionId = PAN_RIGHT2_CMD;
					} else if (actionId == ZOOM_OUT_CMD) {
						actionId = PAN_LEFT2_CMD;
					}
				} else if (manualRotationMode) {
					if (actionId == ZOOM_IN_CMD) {
						actionId = PAN_LEFT2_CMD;
					} else if (actionId == ZOOM_OUT_CMD) {
						actionId = PAN_RIGHT2_CMD;
					}
				} else if (TrackPlayer.isPlaying) {
					if (actionId == ZOOM_IN_CMD) {
						actionId = PAN_RIGHT2_CMD;
					} else if (actionId == ZOOM_OUT_CMD) {
						actionId = PAN_LEFT2_CMD;
					}
				}
				commandAction(actionId);
				repaint();
			}
		}
	}
	private void doubleTap(int x, int y) {
		if (tl.getElementIdAtPointer(touchX, touchY) < 0) {
			if (Configuration.getCfgBitState(Configuration.CFGBIT_MAPTAP_DOUBLE)) {
				if (singleTapTimerTask != null) {
					singleTapTimerTask.cancel();
				}
				if (pointerDraggedMuch) {
					return;
				}
				logger.debug("double tap map");
				pointerActionDone = true;
				commandAction(ZOOM_IN_CMD);
			}
			repaint();
			return;
		} else if (tl.getTouchedElement() == tl.getElementAtPointer(x, y) ){
			int actionId = tl.getActionIdDoubleAtPointer(x, y);
			logger.debug("double tap button: " + actionId + " x: " + x + " y: " + x);
			if (actionId > 0) {
				if (singleTapTimerTask != null) {
					singleTapTimerTask.cancel();
				}
				pointerActionDone = true;
				commandAction(actionId);
				tl.clearTouchedElement();
				repaint();
				return;
			} else {
				singleTap(x, y);
			}
		}
	}
	private void longTap() {
		pointerActionDone = true;
		if (tl.getElementIdAtPointer(touchX, touchY) < 0 && panProjection != null) {							
			if (!pointerDraggedMuch && Configuration.getCfgBitState(Configuration.CFGBIT_MAPTAP_LONG)) {
				logger.debug("long tap map");										
				pickPointEnd=panProjection.inverse(touchX,touchY, pickPointEnd);
				center.radlat=centerPointerPressedN.radlat-(pickPointEnd.radlat-pickPointStart.radlat);
				center.radlon=centerPointerPressedN.radlon-(pickPointEnd.radlon-pickPointStart.radlon);
				Position oPos = new Position(center.radlat, center.radlon,
							     0.0f, 0.0f, 0.0f, 0, 0);
				imageCollector.newDataReady();
				gpsRecenter = false;
				commandAction(ONLINE_INFO_CMD);
			}
			return;
		} else {
			int actionId = tl.getActionIdLongAtPointer(touchX, touchY);
			if (actionId > 0 && tl.getElementAtPointer(touchX, touchY) == tl.getTouchedElement()) {
				logger.debug("long tap button: " + actionId + " x: " + touchX + " y: " + touchY);
				commandAction(actionId);
				repaint();
			}
		}
		tl.clearTouchedElement();
	}
	public Command getDataScreenCommand() {
		return CMDS[DATASCREEN_CMD];
	}
	public Tile getDict(byte zl) {
		return tiles[zl];
	}
	public void setDict(Tile dict, byte zl) {
		tiles[zl] = dict;
		if (zl == 0) {
			Configuration.getStartupPos(center);
			if (center.radlat == 0.0f && center.radlon == 0.0f) {
				dict.getCenter(center);
			}
			if (Configuration.getCfgBitState(Configuration.CFGBIT_SAVED_DESTPOS_VALID)) {
				Node destNode = new Node();
				Configuration.getDestPos(destNode);
				setDestination(new RoutePositionMark(destNode.radlat, destNode.radlon));
			}
			if (pc != null) {
				pc.center = center.copy();
				pc.scale = scale;
				pc.course = course;
			}
		}
		updatePosition();
	}
	public void receiveStatistics(int[] statRecord, byte quality) {
		this.btquality = quality;
		this.statRecord = statRecord;
		repaint();
	}
	public synchronized void locationDecoderEnd() {
		logger.info("enter locationDecoderEnd");
		if (Configuration.getCfgBitState(Configuration.CFGBIT_SND_DISCONNECT)) {
			if (Legend.isValid) {
				GpsMid.mNoiseMaker.playSound("DISCONNECT");
			}
		}
		removeCommand(CMDS[DISCONNECT_GPS_CMD]);
		if (locationProducer == null) {
			logger.info("leave locationDecoderEnd no producer");
			return;
		}
		locationProducer = null;
		notify();
		addCommand(CMDS[CONNECT_GPS_CMD]);
		logger.info("end locationDecoderEnd");
	}
	public void receiveStatus(byte status, int satsReceived) {
		solution = status;
		solutionStr = LocationMsgReceiverList.getCurrentStatusString(status, satsReceived);
		repaint();
	}
	public String getName(int idx) {
		if (idx < 0) {
			return null;
		}
		return namesThread.getName(idx);
	}
	public String getUrl(int idx) {
		if (idx < 0) {
			return null;
		}
		return urlsThread.getUrl(idx);
	}
	public Vector fulltextSearch (String snippet, CancelMonitorInterface cmi) {
		return namesThread.fulltextSearch(snippet, cmi);
	}
	public void requestRedraw() {
		repaint();
	}
	public void newDataReady() {
		if (imageCollector != null) {
			imageCollector.newDataReady();
		}
	}
	public void show() {
		Legend.freeDrawnWayAndAreaSearchImages();
		GpsMid.getInstance().show(this);
		setFullScreenMode(Configuration.getCfgBitState(Configuration.CFGBIT_FULLSCREEN));
		updateLastUserActionTime();
		repaint();
	}
	public void recreateTraceLayout() {
		tl = new TraceLayout(0, 0, getWidth(), getHeight());
	}
	public void resetSize() {
		sizeChanged(getWidth(), getHeight());
	}
	public void locationDecoderEnd(String msg) {
		receiveMessage(msg);
		locationDecoderEnd();
	}
	public PositionMark getDestination() {
		return dest;
	}
	public void setDestination(RoutePositionMark dest) {
		endRouting();
		this.dest = dest;
		pc.dest = dest;
		if (dest != null) {
			logger.info("Setting destination to " + dest.toString());
			if (! Configuration.getCfgBitState(Configuration.CFGBIT_ICONMENUS_ROUTING_OPTIMIZED)) {
				commandAction(SHOW_DEST_CMD);
			}
			if (Configuration.getCfgBitState(Configuration.CFGBIT_AUTOSAVE_DESTPOS)) {
				Configuration.setDestPos(new Node(dest.lat, dest.lon, true));
				Configuration.setCfgBitSavedState(Configuration.CFGBIT_SAVED_DESTPOS_VALID, true);
			}
			movedAwayFromDest = false;
		} else {
			Configuration.setCfgBitSavedState(Configuration.CFGBIT_SAVED_DESTPOS_VALID, false);
			logger.info("Setting destination to null");
		}
	}
	public void endRouting() {
		RouteInstructions.initialRecalcDone = false;
		RouteInstructions.icCountOffRouteDetected = 0;
		RouteInstructions.routeInstructionsHeight = 0;
		RouteInstructions.abortRouteLineProduction();
		setRoute(null);
		setRouteNodes(null);
	}
	public void setRoute(Vector route) {
		synchronized(this) {
			this.route = route;
		}
		if (this.route != null) {
			RouteInstructions.resetOffRoute(this.route, center);
			if (ri == null) {
				ri = new RouteInstructions(this);
			}
			if (Configuration.getContinueMapWhileRouteing() == Configuration.continueMap_At_Route_Line_Creation) {
				resumeImageCollectorAfterRouteCalc();
			}
			ri.newRoute(this.route);
			oldRecalculationTime = System.currentTimeMillis();
		}
		resumeImageCollectorAfterRouteCalc();
		routeCalc=false;
		routeEngine=null;
	}
	private void resumeImageCollectorAfterRouteCalc() {
		try {
			if (imageCollector == null) {
				startImageCollector();
				imageCollector.resume();
			} else if (imageCollector != null) {
				imageCollector.newDataReady();
			}
			repaint();
		} catch (Exception e) {
			logger.exception(Locale.get("trace.InTraceResumeImageCollector"), e);
		}
	}
	public void dropCache() {
		tileReader.dropCache();
		dictReader.dropCache();
		System.gc();
		namesThread.dropCache();
		urlsThread.dropCache(); 
		System.gc();
		if (gpx != null) {
			gpx.dropCache();
		}
	}
	public QueueDataReader getDataReader() {
		return tileReader;
	}
	public QueueDictReader getDictReader() {
		return dictReader;
	}
	public Vector getRouteNodes() {
		return routeNodes;
	}
	public void setRouteNodes(Vector routeNodes) {
		this.routeNodes = routeNodes;
	}
	protected void hideNotify() {
		logger.debug("Hide notify has been called, screen will no longer be updated");
		if (imageCollector != null) {
			imageCollector.suspend();
		}
	}
	protected void showNotify() {
		logger.debug("Show notify has been called, screen will be updated again");
		if (imageCollector != null) {
			imageCollector.resume();
			imageCollector.newDataReady();
		}
	}
	public Vector getRoute() {
		return route;
	}
	public void actionCompleted() {
		boolean reAddCommands = true;
		if (reAddCommands && Configuration.getCfgBitState(Configuration.CFGBIT_FULLSCREEN)) {
			addAllCommands();
		}
	}
	public void showIconMenu() {
		if (traceIconMenu == null) {
			traceIconMenu = new TraceIconMenu(this, this);
		}
		traceIconMenu.show();
	}
	public static void uncacheIconMenu() {
		if (traceIconMenu != null) {
			logger.trace("uncaching TraceIconMenu");
		}
		traceIconMenu = null;
	}
	public void recreateAndShowIconMenu() {
		uncacheIconMenu();
		showIconMenu();
	}
	public void performIconAction(int actionId) {
		updateLastUserActionTime();
		if (routeCalc || GpsMid.getInstance().needsFreeingMemory()) {
			logger.info("low mem: Uncaching traceIconMenu");
			uncacheIconMenu();
		}
		if (actionId != IconActionPerformer.BACK_ACTIONID) {
			commandAction(actionId);
		}
	}
	public static String showDistance(int meters) {
		return showDistance(meters, DISTANCE_GENERIC);
	}
	public static String showDistance(int meters, int type) {
		if (Configuration.getCfgBitState(Configuration.CFGBIT_METRIC)) {
			if (type == DISTANCE_UNKNOWN) {
				return "???m";
			}
			if (Configuration.getCfgBitState(Configuration.CFGBIT_DISTANCE_VIEW) && (type != DISTANCE_ALTITUDE)) {
				if (meters >= 10000) {
					return meters / 1000 + "km";
				} else if (meters < 1000) {
					return meters + "m";
				} else {
					return meters / 1000 + "." + (meters % 1000) / 100 + "km";
				}
			} else {
				return meters + "m";
			}
		} else {
			if (type == DISTANCE_UNKNOWN) {
				return "???yd";
			} else if (type == DISTANCE_ALTITUDE) {
				return Integer.toString((int)(meters / 0.30480 + 0.5)) + "ft";
			} else {
				return Integer.toString((int)(meters / 0.9144 + 0.5)) + "yd";
			}
		}
	}
}

	public void commandAction(Command c, Displayable d) {
		updateLastUserActionTime();
		try {
			if((keyboardLocked) && (d != null)) {
				keyPressed(0);
				return;
			}
			if ((c == CMDS[PAN_LEFT25_CMD]) || (c == CMDS[PAN_RIGHT25_CMD])
					|| (c == CMDS[PAN_UP25_CMD]) || (c == CMDS[PAN_DOWN25_CMD])
					|| (c == CMDS[PAN_LEFT2_CMD]) || (c == CMDS[PAN_RIGHT2_CMD])
					|| (c == CMDS[PAN_UP2_CMD]) || (c == CMDS[PAN_DOWN2_CMD])) {
				int panX = 0; int panY = 0;
				int courseDiff = 0;
				int backLightLevelDiff = 0;
				if (c == CMDS[PAN_LEFT25_CMD]) {
					panX = -25;
				} else if (c == CMDS[PAN_RIGHT25_CMD]) {
					panX = 25;
				} else if (c == CMDS[PAN_UP25_CMD]) {
					panY = -25;
				} else if (c == CMDS[PAN_DOWN25_CMD]) {
					panY = 25;
				} else if (c == CMDS[PAN_LEFT2_CMD]) {
					if (TrackPlayer.isPlaying) {
						TrackPlayer.slower();
					} else if (manualRotationMode) {
						courseDiff=-5;
					} else {
						panX = -2;
					}
					backLightLevelDiff = -25;
				} else if (c == CMDS[PAN_RIGHT2_CMD]) {
					if (TrackPlayer.isPlaying) {
						TrackPlayer.faster();
					} else if (manualRotationMode) {
						courseDiff=5;
					} else {
						panX = 2;
					}
					backLightLevelDiff = 25;
				} else if (c == CMDS[PAN_UP2_CMD]) {
					if (route!=null && Configuration.getCfgBitState(Configuration.CFGBIT_ROUTE_BROWSING)) {
						RouteInstructions.toNextInstruction(1);
					} else {
						panY = -2;
					}
				} else if (c == CMDS[PAN_DOWN2_CMD]) {
					if (route!=null && Configuration.getCfgBitState(Configuration.CFGBIT_ROUTE_BROWSING)) {
						RouteInstructions.toNextInstruction(-1);
					} else {
						panY = 2;
					}
				}
				if (backLightLevelDiff !=0  &&  System.currentTimeMillis() < (lastBackLightOnTime + 2500)) {
					Configuration.setCfgBitState(Configuration.CFGBIT_BACKLIGHT_ON, true, false);
					lastBackLightOnTime = System.currentTimeMillis();
					Configuration.addToBackLightLevel(backLightLevelDiff);
					parent.showBackLightLevel();
				} else if (imageCollector != null) {
					if (Configuration.getCfgBitState(Configuration.CFGBIT_COMPASS_DIRECTION) && compassProducer != null) {
						if (compassDeviation == 360) {
							compassDeviation = 0;
						} else {
							compassDeviation += courseDiff;
							compassDeviation %= 360;
							if (course < 0) {
								course += 360;
							}
						}
					} else {
						if (courseDiff == 360) {
							course = 0; 
						} else {
							course += courseDiff;
							course %= 360;
							if (course < 0) {
								course += 360;
							}
						}
						if (panX != 0 || panY != 0) {
							gpsRecenter = false;
						}
					}
					imageCollector.getCurrentProjection().pan(center, panX, panY);
				}
				gpsRecenter = false;
				return;
			}
			if (c == CMDS[EXIT_CMD]) {
				if (Legend.isValid && gpx.isRecordingTrk()) {
					alert(Locale.get("trace.RecordMode"), Locale.get("trace.PleaseStopRecording") , 2500);
					return;
				}
				if (Legend.isValid) {
					pause();
				}
				parent.exit();
				return;
			}
			if (c == CMDS[START_RECORD_CMD]) {
				try {
					gpx.newTrk(false);
					alert(Locale.get("trace.GpsRecording"), Locale.get("trace.StartingToRecord"), 1250);
				} catch (RuntimeException e) {
					receiveMessage(e.getMessage());
				}
				recordingsMenu = null; 
				return;
			}
			if (c == CMDS[STOP_RECORD_CMD]) {
				gpx.saveTrk(false);
				alert(Locale.get("trace.GpsRecording"), Locale.get("trace.StoppingToRecord"), 1250);
				recordingsMenu = null; 
				return;
			}
			if (c == CMDS[MANAGE_TRACKS_CMD]) {
				if (gpx.isRecordingTrk() && !gpx.isRecordingTrkSuspended()) {
					alert(Locale.get("trace.RecordMode"), Locale.get("trace.YouNeedStopRecording") , 4000);
					return;
				}
			    GuiGpx guiGpx = new GuiGpx(this);
			    guiGpx.show();
			    return;
			}
			if (c == CMDS[REFRESH_CMD]) {
				repaint();
				return;
			}
			if (c == CMDS[CONNECT_GPS_CMD]) {
				if (locationProducer == null) {
					if (TrackPlayer.isPlaying) {
						TrackPlayer.getInstance().stop();
						alert(Locale.get("trace.Trackplayer"), Locale.get("trace.PlayingStopped"), 2500);
					}
					Thread thread = new Thread(this,"LocationProducer init");
					thread.start();
				}
				return;
			}
			if (c == CMDS[DISCONNECT_GPS_CMD]) {
				if (locationProducer != null) {
					locationProducer.close();
				}
				return;
			}
			if (c == CMDS[TOGGLE_GPS_CMD]) {
				if (isGpsConnected()) {
					commandAction(DISCONNECT_GPS_CMD);
				} else {
					commandAction(CONNECT_GPS_CMD);					
				}
				return;
			}
			if (c == CMDS[SEARCH_CMD]) {
				GuiSearch guiSearch = new GuiSearch(this);
				guiSearch.show();
				return;
			}
			if (c == CMDS[ENTER_WAYP_CMD]) {
				if (gpx.isLoadingWaypoints()) {
					showAlertLoadingWpt();
				} else {
					GuiWaypointEnter gwpe = new GuiWaypointEnter(this);
					gwpe.show();
				}
				return;
			}
			if (c == CMDS[MAN_WAYP_CMD]) {
				if (gpx.isLoadingWaypoints()) {
					showAlertLoadingWpt();
				} else {
					GuiWaypoint gwp = new GuiWaypoint(this);
					gwp.show();
				}
				return;
			}
			if (c == CMDS[MAPFEATURES_CMD]) {
				GuiMapFeatures gmf = new GuiMapFeatures(this);
				gmf.show();
				repaint();
				return;
			}
			if (c == CMDS[OVERVIEW_MAP_CMD]) {
				GuiOverviewElements ovEl = new GuiOverviewElements(this);
				ovEl.show();
				repaint();
				return;
			}
			if (c == CMDS[SEND_MESSAGE_CMD]) {
				GuiSendMessage sendMsg = new GuiSendMessage(this);
				sendMsg.show();
				repaint();
				return;
			}
			if (c == CMDS[RECORDINGS_CMD]) {
				if (recordingsMenu == null) {
					boolean hasJSR120 = Configuration.hasDeviceJSR120();
					int noElements = 3;
					noElements += 2;
					if (hasJSR120) {
						noElements++;
					}
					int idx = 0;
					String[] elements = new String[noElements];
					if (gpx.isRecordingTrk()) {
						elements[idx++] = Locale.get("trace.StopGpxTracklog");
					} else {
						elements[idx++] = Locale.get("trace.StartGpxTracklog");
					}
					elements[idx++] = Locale.get("trace.SaveWaypoint");
					elements[idx++] = Locale.get("trace.EnterWaypoint");
					elements[idx++] = Locale.get("trace.TakePictures");
					if (audioRec.isRecording()) {
						elements[idx++] = Locale.get("trace.StopAudioRecording");
					} else {
						elements[idx++] = Locale.get("trace.StartAudioRecording");
					}
					if (hasJSR120) {
						elements[idx++] = Locale.get("trace.SendSMSMapPos");
					}
					recordingsMenu = new List(Locale.get("trace.Recordings"),Choice.IMPLICIT,elements,null);
					recordingsMenu.addCommand(CMDS[OK_CMD]);
					recordingsMenu.addCommand(CMDS[BACK_CMD]);
					recordingsMenu.setSelectCommand(CMDS[OK_CMD]);
					parent.show(recordingsMenu);
					recordingsMenu.setCommandListener(this);
				}
				if (recordingsMenu != null) {
					recordingsMenu.setSelectedIndex(0, true);
					parent.show(recordingsMenu);
				}
				return;
			}
			if (c == CMDS[ROUTINGS_CMD]) {
				if (routingsMenu == null) {
					String[] elements = new String[4];
					if (routeCalc || route != null) {
						elements[0] = Locale.get("trace.StopRouting");
					} else {
						elements[0] = Locale.get("trace.CalculateRoute");
					}
					elements[1] = Locale.get("trace.SetDestination");
					elements[2] = Locale.get("trace.ShowDestination");
					elements[3] = Locale.get("trace.ClearDestination");
					routingsMenu = new List(Locale.get("trace.Routing2"), Choice.IMPLICIT, elements, null);
					routingsMenu.addCommand(CMDS[OK_CMD]);
					routingsMenu.addCommand(CMDS[BACK_CMD]);
					routingsMenu.setSelectCommand(CMDS[OK_CMD]);
					routingsMenu.setCommandListener(this);
				}
				if (routingsMenu != null) {
					routingsMenu.setSelectedIndex(0, true);
					parent.show(routingsMenu);
				}
				return;
			}
			if (c == CMDS[SAVE_WAYP_CMD]) {
				if (gpx.isLoadingWaypoints()) {
					showAlertLoadingWpt();
				} else {
					PositionMark posMark = null;
					if (gpsRecenter) {
						posMark = new PositionMark(center.radlat, center.radlon,
								(int)pos.altitude, pos.timeMillis,
								 (byte)-1,  (byte)-1,
								 (byte)-1,  (byte)-1);
					} else {
						posMark = new PositionMark(center.radlat, center.radlon,
								PositionMark.INVALID_ELEVATION,
								pos.timeMillis,  (byte)-1,
								 (byte)-1,  (byte)-1,
								 (byte)-1);
					}
						showGuiWaypointSave(posMark);
				}
				return;
			}
			if (c == CMDS[ONLINE_INFO_CMD]) {
					Position oPos = new Position(center.radlat, center.radlon,
							0.0f, 0.0f, 0.0f, 0, 0);
					GuiWebInfo gWeb = new GuiWebInfo(this, oPos, pc);
					gWeb.show();
					alert(Locale.get("trace.NoOnlineCapa"),
					      Locale.get("trace.SetAppGeneric") +
					      Locale.get("trace.PropertiesFile"),
							Alert.FOREVER);
			}
			if (c == CMDS[BACK_CMD]) {
				show();
				return;
			}
			if (c == CMDS[OK_CMD]) {
				if (d == recordingsMenu) {
					 switch (recordingsMenu.getSelectedIndex()) {
			            case 0: {
		    				if ( gpx.isRecordingTrk() ) {
		    					commandAction(STOP_RECORD_CMD);
								if (! Configuration.getCfgBitState(Configuration.CFGBIT_GPX_ASK_TRACKNAME_STOP)) {
							    	show();
								}
		    				} else {
		    					commandAction(START_RECORD_CMD);
								if (! Configuration.getCfgBitState(Configuration.CFGBIT_GPX_ASK_TRACKNAME_START)) {
							    	show();
								}
		    				}
			            	break;
			            }
			            case 1: {
			            	commandAction(SAVE_WAYP_CMD);
							break;
			            }
			            case 2: {
			            	commandAction(ENTER_WAYP_CMD);
							break;
			            }
			            case 3: {
			            	commandAction(CAMERA_CMD);
			            	break;
			            }
			            case 4: {
			            	show();
			            	commandAction(TOGGLE_AUDIO_REC);
			            	break;
			            }
			            case 5: {
			            	commandAction(SEND_MESSAGE_CMD);
			            	break;
			            }
			            case 3: {
			            	commandAction(SEND_MESSAGE_CMD);
			            	break;
			            }
					 }
				}
				if (d == routingsMenu) {
					show();
					switch (routingsMenu.getSelectedIndex()) {
					case 0: {
						if (routeCalc || route != null) {
							commandAction(ROUTING_STOP_CMD);
						} else {
							commandAction(ROUTING_START_WITH_MODE_SELECT_CMD);
						}
						break;
					}
					case 1: {
						commandAction(SET_DEST_CMD);
						break;
					}
					case 2: {
						commandAction(SHOW_DEST_CMD);
						break;
					}
					case 3: {
						commandAction(CLEAR_DEST_CMD);
						break;
					}
					}
				}
				return;
			}
			if (c == CMDS[CAMERA_CMD]) {
				try {
					Class GuiCameraClass = Class.forName("de.ueller.midlet.gps.GuiCamera");
					Object GuiCameraObject = GuiCameraClass.newInstance();
					GuiCameraInterface cam = (GuiCameraInterface)GuiCameraObject;
					cam.init(this);
					cam.show();
				} catch (ClassNotFoundException cnfe) {
					logger.exception(Locale.get("trace.YourPhoneNoCamSupport"), cnfe);
				}
				return;
			}
			if (c == CMDS[TOGGLE_AUDIO_REC]) {
				if (audioRec.isRecording()) {
					audioRec.stopRecord();
				} else {
					audioRec.startRecorder();
				}
				recordingsMenu = null; 
				return;
			}
			if (c == CMDS[ROUTING_TOGGLE_CMD]) {
				if (routeCalc || route != null) {
					commandAction(ROUTING_STOP_CMD);
				} else {
					commandAction(ROUTING_START_WITH_MODE_SELECT_CMD);
				}
				return;
			}
			if (c == CMDS[ROUTING_START_WITH_MODE_SELECT_CMD]) {
				gpsRecenter = true;
				gpsRecenterInvalid = true;
				gpsRecenterStale = true;
				GuiRoute guiRoute = new GuiRoute(this, false);
				if (Configuration.getCfgBitSavedState(Configuration.CFGBIT_DONT_ASK_FOR_ROUTING_OPTIONS)) {
					commandAction(ROUTING_START_CMD);
				} else {
					guiRoute.show();
				}
				return;
			}
			if (c == CMDS[ROUTING_START_CMD]) {
				if (! routeCalc || RouteLineProducer.isRunning()) {
					routeCalc = true;
					if (Configuration.getContinueMapWhileRouteing() != Configuration.continueMap_Always ) {
	  				   stopImageCollector();
					}
					RouteInstructions.resetOffRoute(route, center);
					RoutePositionMark routeSource = new RoutePositionMark(center.radlat, center.radlon);
					logger.info("Routing source: " + routeSource);
					routeNodes=new Vector();
					routeEngine = new Routing(tiles, this);
					routeEngine.solve(routeSource, dest);
				}
				routingsMenu = null; 
				return;
			}
			if (c == CMDS[ROUTING_STOP_CMD]) {
				NoiseMaker.stopPlayer();
				if (routeCalc) {
					if (routeEngine != null) {
						routeEngine.cancelRouting();
					}
					alert(Locale.get("trace.RouteCalculation"), Locale.get("trace.Cancelled"), 1500);
				} else {
					alert(Locale.get("trace.Routing"), Locale.get("generic.Off"), 750);
				}
				endRouting();
				routingsMenu = null; 
				synchronized (this) {
					if (imageCollector != null) {
						imageCollector.newDataReady();
					}
				}
				routingsMenu = null; 
				return;
			}
			if (c == CMDS[ZOOM_IN_CMD]) {
				scale = scale / 1.5f;
				autoZoomed = false;
				return;
			}
			if (c == CMDS[ZOOM_OUT_CMD]) {
				scale = scale * 1.5f;
				autoZoomed = false;
				return;
			}
			if (c == CMDS[MANUAL_ROTATION_MODE_CMD]) {
				manualRotationMode = !manualRotationMode;
				if (manualRotationMode) {
					if (hasPointerEvents()) {
						alert(Locale.get("trace.ManualRotation"), Locale.get("trace.ChangeCourse"), 3000);
					} else {
						alert(Locale.get("trace.ManualRotation"), Locale.get("trace.ChangeCourseWithLeftRightKeys"), 3000);
					}
				} else {
					alert(Locale.get("trace.ManualRotation"), Locale.get("generic.Off"), 750);
				}
				return;
			}
			if (c == CMDS[TOGGLE_OVERLAY_CMD]) {
				showAddons++;
				repaint();
				return;
			}
			if (c == CMDS[TOGGLE_BACKLIGHT_CMD]) {
				Configuration.setCfgBitState(Configuration.CFGBIT_BACKLIGHT_ON,
									!(Configuration.getCfgBitState(Configuration.CFGBIT_BACKLIGHT_ON)),
									false);
				lastBackLightOnTime = System.currentTimeMillis();
				parent.showBackLightLevel();
				return;
			}
			if (c == CMDS[TOGGLE_FULLSCREEN_CMD]) {
				boolean fullScreen = !Configuration.getCfgBitState(Configuration.CFGBIT_FULLSCREEN);
				Configuration.setCfgBitState(Configuration.CFGBIT_FULLSCREEN, fullScreen, false);
				setFullScreenMode(fullScreen);
				return;
			}
			if (c == CMDS[TOGGLE_MAP_PROJ_CMD]) {
				if (manualRotationMode) {
					if (Configuration.getCfgBitState(Configuration.CFGBIT_COMPASS_DIRECTION) && compassProducer != null) {
						compassDeviation = 0;
					} else {
						course = 0;
					}
					alert(Locale.get("trace.ManualRotation"), Locale.get("trace.ManualToNorth"), 750);
				} else {
					alert(Locale.get("guidiscover.MapProjection"), ProjFactory.nextProj(), 750);
				}
				synchronized (this) {
					if (imageCollector != null) {
						imageCollector.newDataReady();
					}
				}
				return;
			}
			if (c == CMDS[TOGGLE_KEY_LOCK_CMD]) {
				keyboardLocked = !keyboardLocked;
				if (keyboardLocked) {
					keyPressed(0);
				} else {
					alert(Locale.get("trace.GpsMid"), hasPointerEvents() ? Locale.get("trace.KeysAndTouchscreenUnlocked") : Locale.get("trace.KeysUnlocked"), 1000);
				}
				return;
			}
			if (c == CMDS[TOGGLE_RECORDING_CMD]) {
				if ( gpx.isRecordingTrk() ) {
					commandAction(STOP_RECORD_CMD);
				} else {
					commandAction(START_RECORD_CMD);
				}
				return;
			}
			if (c == CMDS[TOGGLE_RECORDING_SUSP_CMD]) {
				if (gpx.isRecordingTrk()) {
					if ( gpx.isRecordingTrkSuspended() ) {
						alert(Locale.get("trace.GpsRecording"), Locale.get("trace.ResumingRecording"), 1000);
						gpx.resumeTrk();
					} else {
						alert(Locale.get("trace.GpsRecording"), Locale.get("trace.SuspendingRecording"), 1000);
						gpx.suspendTrk();
					}
				}
				return;
			}
			if (c == CMDS[RECENTER_GPS_CMD]) {
				gpsRecenter = true;
				gpsRecenterInvalid = true;
				gpsRecenterStale = true;
				autoZoomed = true;
				if (pos.latitude != 0.0f) {
					receivePosition(pos);
				}
				newDataReady();
				return;
			}
			if (c == CMDS[SHOW_DEST_CMD]) {
				if (dest != null) {
					gpsRecenter = false;
					prevPositionNode = center.copy(); 
					center.setLatLonRad(dest.lat, dest.lon);						
					movedAwayFromDest = false;
					updatePosition();
				}
				else {
					alert(Locale.get("trace.ShowDestination"), Locale.get("trace.DestinationNotSpecifiedYet"), 3000);
				}
				return;
			}
			if (c == CMDS[SHOW_PREVIOUS_POSITION_CMD]) {
				if (prevPositionNode != null) {
					gpsRecenter = false;
					center.setLatLon(prevPositionNode);						
					updatePosition();
				}
			}
			if (c == CMDS[DATASCREEN_CMD]) {
				showNextDataScreen(DATASCREEN_NONE);
				return;
			}
			if (c == CMDS[ICON_MENU] && Configuration.getCfgBitState(Configuration.CFGBIT_ICONMENUS)) {
				showIconMenu();
				return;
			}
			if (c == CMDS[SETUP_CMD]) {
				new GuiDiscover(parent);
				return;
			}
			if (c == CMDS[ABOUT_CMD]) {
				new Splash(parent, GpsMid.initDone);
				return;
			}
			if (c == CMDS[CELLID_LOCATION_CMD]) {
				if (Configuration.getLocationProvider() == Configuration.LOCATIONPROVIDER_SECELL && locationProducer != null) {
						locationProducer.triggerPositionUpdate();
						newDataReady();
				} else {
					if (cellIDLocationProducer == null) {
						cellIDLocationProducer = new SECellID();
						if (cellIDLocationProducer != null && !cellIDLocationProducer.init(this)) {
							logger.info("Failed to initialise CellID location producer");
						}
					}
					if (cellIDLocationProducer != null) {
						cellIDLocationProducer.triggerPositionUpdate();
						newDataReady();
					}
				}
				return;
			}
			if (c == CMDS[MANUAL_LOCATION_CMD]) {
				Position setpos = new Position(center.radlat / MoreMath.FAC_DECTORAD,
    								center.radlon / MoreMath.FAC_DECTORAD,
								    PositionMark.INVALID_ELEVATION, 0.0f, 0.0f, 1,
							            System.currentTimeMillis(), Position.TYPE_MANUAL);
				gpsRecenter = true;
				autoZoomed = true;
				receivePosition(setpos);
				receiveStatus(LocationMsgReceiver.STATUS_MANUAL, 0);
				newDataReady();
				return;
			}
			if (! routeCalc) {
				if (c == CMDS[RETRIEVE_XML]) {
					if (Legend.enableEdits) {
						if ((pc.actualWay != null) && (getUrl(pc.actualWay.urlIdx) != null)) {
							parent.alert ("Url", "Url: " + getUrl(pc.actualWay.urlIdx), Alert.FOREVER);
						}
						if ((pc.actualWay != null) && (pc.actualWay instanceof EditableWay)) {
							EditableWay eway = (EditableWay)pc.actualWay;
							GUIosmWayDisplay guiWay = new GUIosmWayDisplay(eway, pc.actualSingleTile, this);
							guiWay.show();
							guiWay.refresh();
						}
					} else {
						parent.alert("Editing", "Editing support was not enabled in Osm2GpsMid", Alert.FOREVER);
					}
				}
				if (c == CMDS[RETRIEVE_NODE]) {
					if (Legend.enableEdits) {
						GuiOSMPOIDisplay guiNode = new GuiOSMPOIDisplay(-1, null,
								center.radlat, center.radlon, this);
						guiNode.show();
						guiNode.refresh();
					} else {
						logger.error(Locale.get("trace.EditingIsNotEnabled"));
					}
				}
				if (c == CMDS[EDIT_ADDR_CMD]) {
					if (Legend.enableEdits) {
						String streetName = "";
						if ((pc != null) && (pc.actualWay != null)) {
							streetName = getName(pc.actualWay.nameIdx);
						}
						GuiOSMAddrDisplay guiAddr = new GuiOSMAddrDisplay(-1, streetName, null,
								center.radlat, center.radlon, this);
						guiAddr.show();
					} else {
						logger.error(Locale.get("trace.EditingIsNotEnabled"));
					}
				}
				if (c == CMDS[RETRIEVE_XML] || c == CMDS[RETRIEVE_NODE] || c == CMDS[EDIT_ADDR_CMD]) {
					alert("No online capabilites",
					      Locale.get("trace.SetAppGeneric") +
					      Locale.get("trace.PropertiesFile"),
						Alert.FOREVER);
				}
				if (c == CMDS[SET_DEST_CMD]) {
					RoutePositionMark pm1 = new RoutePositionMark(center.radlat, center.radlon);
					setDestination(pm1);
					return;
				}
				if (c == CMDS[CLEAR_DEST_CMD]) {
					setDestination(null);
					return;
				}
			} else {
				alert(Locale.get("trace.Error"), Locale.get("trace.CurrentlyInRouteCalculation"), 2000);
			}
		} catch (Exception e) {
 			logger.exception(Locale.get("trace.InTraceCommandAction"), e);
		}
	}
	private void startImageCollector() throws Exception {
		logger.info("Starting ImageCollector");
		Images images = new Images();
		pc = new PaintContext(this, images);
		imageCollector = new ImageCollector(tiles, this.getWidth(), this.getHeight(), this, images);
		pc.center = center.copy();
		pc.scale = scale;
		pc.xSize = this.getWidth();
		pc.ySize = this.getHeight();
	}
	private void stopImageCollector(){
		logger.info("Stopping ImageCollector");
		cleanup();
		if (imageCollector != null ) {
			imageCollector.stop();
			imageCollector=null;
		}
		System.gc();
	}
	public void startup() throws Exception {
		namesThread = new Names();
		urlsThread = new Urls();
		new DictReader(this);
		if (Configuration.getCfgBitState(Configuration.CFGBIT_AUTO_START_GPS)) {
			Thread thread = new Thread(this, "Trace");
			thread.start();
		}
		tileReader = new QueueDataReader(this);
		dictReader = new QueueDictReader(this);
		this.gpx = new Gpx();
		this.audioRec = new AudioRecorder();
		setDict(gpx, (byte)5);
		startImageCollector();
		recreateTraceLayout();
	}
	public void shutdown() {
		if (gpx != null) {
			gpx.saveTrk(true);
		}
		logger.debug("Shutdown: stopImageCollector");
		stopImageCollector();
		if (namesThread != null) {
			logger.debug("Shutdown: namesThread");
			namesThread.stop();
			namesThread = null;
		}
		if (urlsThread != null) {
			urlsThread.stop();
			urlsThread = null;
		}
		if (dictReader != null) {
			logger.debug("Shutdown: dictReader");
			dictReader.shutdown();
			dictReader = null;
		}
		if (tileReader != null) {
			logger.debug("Shutdown: tileReader");
			tileReader.shutdown();
			tileReader = null;
		}
		if (locationProducer != null) {
			logger.debug("Shutdown: locationProducer");
			locationProducer.close();
		}
		if (TrackPlayer.isPlaying) {
			logger.debug("Shutdown: TrackPlayer");
			TrackPlayer.getInstance().stop();
		}
	}
	public void sizeChanged(int w, int h) {
		updateLastUserActionTime();
		if (imageCollector != null) {
			logger.info("Size of Canvas changed to " + w + "|" + h);
			stopImageCollector();
			try {
				startImageCollector();
				imageCollector.resume();
				imageCollector.newDataReady();
			} catch (Exception e) {
				logger.exception(Locale.get("trace.CouldNotReinitialiseImageCollector"), e);
			}
			updatePosition();
		}
		tl = new TraceLayout(0, 0, w, h);
	}
	protected void paint(Graphics g) {
		logger.debug("Drawing Map screen");
		try {
			int yc = 1;
			int la = 18;
			getPC();
			g.setColor(Legend.COLORS[Legend.COLOR_MAP_BACKGROUND]);
			g.fillRect(0, 0, this.getWidth(), this.getHeight());
			pc.g = g;
			if (imageCollector != null) {
				Node drawnCenter = imageCollector.paint(pc);
				if (route != null && ri != null && pc.lineP2 != null) {
					pc.getP().forward(drawnCenter.radlat, drawnCenter.radlon, pc.lineP2);
					int maxAllowedMapMoveOffs = Math.min(pc.xSize/2, pc.ySize/2);
					if ( Math.abs(pc.lineP2.x - pc.getP().getImageCenter().x) < maxAllowedMapMoveOffs
						 &&
						 Math.abs(pc.lineP2.y - pc.getP().getImageCenter().y) < maxAllowedMapMoveOffs
					) {
						ri.showRoute(pc, drawnCenter,imageCollector.xScreenOverscan,imageCollector.yScreenOverscan);
					}
				}
			} else {
				tl.ele[TraceLayout.WAYNAME].setText(" ");
			}
			if (dest != null) {
				float distance = ProjMath.getDistance(dest.lat, dest.lon, center.radlat, center.radlon);
				atDest = (distance < 25);
				if (atDest) {
					if (movedAwayFromDest && Configuration.getCfgBitState(Configuration.CFGBIT_SND_DESTREACHED)) {
						GpsMid.mNoiseMaker.playSound(RouteSyntax.getInstance().getDestReachedSound(), (byte) 7, (byte) 1);
					}
				} else if (!movedAwayFromDest) {
					movedAwayFromDest = true;
				}
			}
			speeding = false;
			int maxSpeed = 0;
			if (gpsRecenter && actualSpeedLimitWay != null) {
				maxSpeed = actualSpeedLimitWay.getMaxSpeed();
				if (Configuration.getCfgBitState(Configuration.CFGBIT_MAXSPEED_WINTER)
                		&& (actualSpeedLimitWay.getMaxSpeedWinter() > 0)) {
					maxSpeed = actualSpeedLimitWay.getMaxSpeedWinter();
                }
				if (maxSpeed != 0 && speed > (maxSpeed + Configuration.getSpeedTolerance()) ) {
					speeding = true;
				}
			}
			if (speeding && Configuration.getCfgBitState(Configuration.CFGBIT_SPEEDALERT_SND)) {
				if ( (System.currentTimeMillis() - lastTimeOfSpeedingSound) > 10000 ) {
					lastTimeOfSpeedingSound = System.currentTimeMillis();
					GpsMid.mNoiseMaker.immediateSound(RouteSyntax.getInstance().getSpeedLimitSound());
				}
			}
			g.setColor(Legend.COLOR_MAP_TEXT);
			switch (showAddons) {
			case 1:
				yc = showMemory(g, yc, la);
				break;
			case 2:
				yc = showConnectStatistics(g, yc, la);
				break;
			default:
				showAddons = 0;
				if (Configuration.getCfgBitState(Configuration.CFGBIT_SHOW_SCALE_BAR)) {
					if (pc != null) {
						tl.calcScaleBarWidth(pc);
						tl.ele[TraceLayout.SCALEBAR].setText(" ");
					}
				}
				setPointOfTheCompass();
			}
			showMovement(g);
			LayoutElement eSolution = tl.ele[TraceLayout.SOLUTION];
			LayoutElement eRecorded = tl.ele[TraceLayout.RECORDED_COUNT];
			if (gpx.isRecordingTrk()) {
				if (gpx.isRecordingTrkSuspended()) {
					eRecorded.setColor(Legend.COLORS[Legend.COLOR_RECORDING_SUSPENDED_TEXT]); 
				} else {
					eRecorded.setColor(Legend.COLORS[Legend.COLOR_RECORDING_ON_TEXT]); 
				}
				eRecorded.setText(gpx.getTrkPointCount() + Locale.get("trace.r"));
			}
			if (TrackPlayer.isPlaying) {
				eSolution.setText(Locale.get("trace.Replay"));
			} else {
				if (locationProducer == null && !(solution == LocationMsgReceiver.STATUS_CELLID ||
										  solution == LocationMsgReceiver.STATUS_MANUAL)) {
					eSolution.setText(Locale.get("solution.Off"));
				} else {
					eSolution.setText(solutionStr);
				}
			}
			LayoutElement e = tl.ele[TraceLayout.CELLID];
			if (SECellLocLogger.isCellIDLogging() > 0) {
				if (SECellLocLogger.isCellIDLogging() == 2) {
					e.setColor(Legend.COLORS[Legend.COLOR_CELLID_LOG_ON_TEXT]); 
				} else {
					e.setColor(Legend.COLORS[Legend.COLOR_CELLID_LOG_ON_ATTEMPTING_TEXT]); 
				}
				e.setText(Locale.get("trace.cellIDs"));
			}
			e = tl.ele[TraceLayout.AUDIOREC];
			if (audioRec.isRecording()) {
				e.setColor(Legend.COLORS[Legend.COLOR_AUDIOREC_TEXT]); 
				e.setText(Locale.get("trace.AudioRec"));
			}
			if (pc != null) {
				showDestination(pc);
			}
			if (speed > 0 &&
					Configuration.getCfgBitState(Configuration.CFGBIT_SHOW_SPEED_IN_MAP)) {
				if (Configuration.getCfgBitState(Configuration.CFGBIT_METRIC)) {
					tl.ele[TraceLayout.SPEED_CURRENT].setText(" " + Integer.toString(speed) + Locale.get("trace.kmh"));
				} else {
					tl.ele[TraceLayout.SPEED_CURRENT].setText(" " + Integer.toString((int)(speed / 1.609344f)) + " mph");
				}
			}
			if (Configuration.getCfgBitState(Configuration.CFGBIT_SHOW_ALTITUDE_IN_MAP)
				&& locationProducer != null
				&& LocationMsgReceiverList.isPosValid(solution)
			) {
				tl.ele[TraceLayout.ALTITUDE].setText(showDistance(altitude, DISTANCE_ALTITUDE));
			}
			if (dest != null && (route == null || (!RouteLineProducer.isRouteLineProduced() && !RouteLineProducer.isRunning()) ) 
				&& Configuration.getCfgBitState(Configuration.CFGBIT_SHOW_AIR_DISTANCE_IN_MAP)) {
				e = Trace.tl.ele[TraceLayout.ROUTE_DISTANCE];
				e.setBackgroundColor(Legend.COLORS[Legend.COLOR_RI_DISTANCE_BACKGROUND]);
				double distLine = ProjMath.getDistance(center.radlat, center.radlon, dest.lat, dest.lon);
				e.setText(Locale.get("trace.Air") + showDistance((int) distLine, DISTANCE_AIR));
			}
			if (Configuration.getCfgBitState(Configuration.CFGBIT_SHOW_CLOCK_IN_MAP)) {
				e = tl.ele[TraceLayout.CURRENT_TIME]; 
				e.setText(DateTimeTools.getClock(System.currentTimeMillis(), true));
				tl.ele[TraceLayout.ROUTE_OFFROUTE].setVRelative(e);
			}
			setSpeedingSign(maxSpeed);
			if (hasPointerEvents()) {
				tl.ele[TraceLayout.ZOOM_IN].setText("+");
				tl.ele[TraceLayout.ZOOM_OUT].setText("-");
				tl.ele[TraceLayout.RECENTER_GPS].setText("|");
				e = tl.ele[TraceLayout.SHOW_DEST];
				if (atDest && prevPositionNode != null) {
					e.setText("<");
					e.setActionID(SHOW_PREVIOUS_POSITION_CMD);
				} else {
					e.setText(">");
					e.setActionID(SHOW_DEST_CMD + (Trace.SET_DEST_CMD << 16) );					
				}
			}
			e = tl.ele[TraceLayout.TITLEBAR];
			if (currentTitleMsgOpenCount != 0) {
				e.setText(currentTitleMsg);
				synchronized (this) {
					if (setTitleMsgTimeout != 0) {
						TimerTask timerT = new TimerTask() {
							public synchronized void run() {
								currentTitleMsgOpenCount--;
								lastTitleMsg = currentTitleMsg;
								if (currentTitleMsgOpenCount == 0) {
									logger.debug("clearing title");
									repaint();
								}
							}
						};
						GpsMid.getTimer().schedule(timerT, setTitleMsgTimeout);
						setTitleMsgTimeout = 0;
					}
				}
			}
			tl.paint(g);
			if (currentAlertsOpenCount > 0) {
				showCurrentAlert(g);
			}
		} catch (Exception e) {
			logger.silentexception("Unhandled exception in the paint code", e);
		}
	}
	public void showGuiWaypointSave(PositionMark posMark) {
    	if (guiWaypointSave == null) {
    		guiWaypointSave = new GuiWaypointSave(this);
    	}
    	if (guiWaypointSave != null) {
    		guiWaypointSave.setData(posMark);
    		guiWaypointSave.show();
    	}
	}
	private void showAlertLoadingWpt() {
		alert("Way points", "Way points are not ready yet.", 3000);
	}
	private void showCurrentAlert(Graphics g) {
		Font font = g.getFont();
		Font titleFont = Font.getFont(font.getFace(), Font.STYLE_BOLD, font.getSize());
		int fontHeight = font.getHeight();
		int y = titleFont.getHeight() + 2 + fontHeight;
		int extraWidth = font.charWidth('W');
		int alertWidth = titleFont.stringWidth(currentAlertTitle);
		int maxWidth = getWidth() - extraWidth;
		for (int i = 0; i <= 1; i++) {
			int nextSpaceAt = 0;
			int prevSpaceAt = 0;
			int start = 0;
			do {
				int width = 0;
				while (width < maxWidth && nextSpaceAt <= currentAlertMessage.length() ) {
					prevSpaceAt = nextSpaceAt;
					nextSpaceAt = currentAlertMessage.indexOf(' ', nextSpaceAt);
					if (nextSpaceAt == -1) {
						nextSpaceAt = currentAlertMessage.length();
					}
					width = font.substringWidth(currentAlertMessage, start, nextSpaceAt - start);
					nextSpaceAt++;
				}
				nextSpaceAt--;
				while (width > maxWidth) {
					if (prevSpaceAt > start && nextSpaceAt > prevSpaceAt) {
						nextSpaceAt = prevSpaceAt;
					} else {
						nextSpaceAt--;
					}
					width = font.substringWidth(currentAlertMessage, start, nextSpaceAt - start);
				}
				if (alertWidth < width ) {
					alertWidth = width;
				}
				if (i==1) {
					g.drawSubstring(currentAlertMessage, start, nextSpaceAt - start,
							getWidth()/2, y, Graphics.TOP|Graphics.HCENTER);
				}
				y += fontHeight;
				start = nextSpaceAt;
			} while (nextSpaceAt < currentAlertMessage.length() );
			if (i == 0) {
				alertWidth += extraWidth;
				int alertHeight = y;
				int alertTop = (getHeight() - alertHeight) /2;
				int alertLeft = (getWidth() - alertWidth) / 2;
				g.setColor(Legend.COLORS[Legend.COLOR_ALERT_BACKGROUND]);
				g.fillRect(alertLeft, alertTop, alertWidth, alertHeight);
				g.setColor(Legend.COLORS[Legend.COLOR_ALERT_TITLE_BACKGROUND]);
				g.fillRect(alertLeft, alertTop, alertWidth, fontHeight + 3);
				g.setColor(Legend.COLORS[Legend.COLOR_ALERT_BORDER]);
				g.setStrokeStyle(Graphics.SOLID);
				g.drawRect(alertLeft, alertTop, alertWidth, fontHeight + 3); 
				g.drawRect(alertLeft, alertTop, alertWidth, alertHeight); 
				y = alertTop + 2; 
				g.setFont(titleFont);
				g.setColor(Legend.COLORS[Legend.COLOR_ALERT_TEXT]);
				g.drawString(currentAlertTitle, getWidth()/2, y , Graphics.TOP|Graphics.HCENTER);
				g.setFont(font);
				y += (fontHeight * 3 / 2);
			}
		} 
		synchronized (this) {
			if (setAlertTimeout != 0) {
				TimerTask timerT = new TimerTask() {
					public synchronized void run() {
						currentAlertsOpenCount--;
						if (currentAlertsOpenCount == 0) {
							logger.debug("clearing alert");
							repaint();
						}
					}
				};
				GpsMid.getTimer().schedule(timerT, setAlertTimeout);
				setAlertTimeout = 0;
			}
		}
	}
	private void setSpeedingSign(int maxSpeed) {
		if (Configuration.getCfgBitState(Configuration.CFGBIT_SPEEDALERT_VISUAL)
			&&
			(
				speeding
				||
				(System.currentTimeMillis() - startTimeOfSpeedingSign) < 3000
			)
		) {
			if (speeding) {
				speedingSpeedLimit = maxSpeed;
				startTimeOfSpeedingSign = System.currentTimeMillis();
			}
			if (Configuration.getCfgBitState(Configuration.CFGBIT_METRIC)) {
				tl.ele[TraceLayout.SPEEDING_SIGN].setText(Integer.toString(speedingSpeedLimit));
			} else {
				tl.ele[TraceLayout.SPEEDING_SIGN].setText(Integer.toString((int)(speedingSpeedLimit / 1.609344f)));
			}
		} else {
			startTimeOfSpeedingSign = 0;
		}
	}
	private void getPC() {
			pc.course = course;
			pc.scale = scale;
			pc.center = center.copy();
			pc.dest = dest;
	}
	public void cleanup() {
		namesThread.cleanup();
		urlsThread.cleanup();
		tileReader.incUnusedCounter();
		dictReader.incUnusedCounter();
	}
	public void searchNextRoutableWay(RoutePositionMark pm) throws Exception {
		PaintContext pc = new PaintContext(this, null);
		pc.squareDstToActualRoutableWay = Float.MAX_VALUE;
		pc.xSize = 100;
		pc.ySize = 100;
		Projection p;
		do {
			p = new Proj2D(new Node(pm.lat,pm.lon, true),5000,pc.xSize,pc.ySize);
			pc.setP(p);
			for (int i=0; i<4; i++) {
				tiles[i].walk(pc, Tile.OPT_WAIT_FOR_LOAD | Tile.OPT_FIND_CURRENT);
			}
			if (pc.actualRoutableWay != null) {
				break;
			}
			pc.xSize += 100;
			pc.ySize += 100;
		} while(MoreMath.dist(p.getMinLat(), p.getMinLon(), p.getMinLat(), p.getMaxLon()) < 500); 
		Way w = pc.actualRoutableWay;
		pm.setEntity(w, pc.currentPos.nodeLat, pc.currentPos.nodeLon);
	}
	private void setPointOfTheCompass() {
		String c = "";
		if (ProjFactory.getProj() != ProjFactory.NORTH_UP
				&& Configuration.getCfgBitState(Configuration.CFGBIT_SHOW_POINT_OF_COMPASS)) {
			c = Configuration.getCompassDirection(course);
		}
		if (tl.bigOnScreenButtons && c.length() <= 1) {
			if (ProjFactory.getProj() == ProjFactory.NORTH_UP) {
				c = "(" + Configuration.getCompassDirection(0) + ")";
			} else {
				c = " " + c + " ";
			}
		}
		tl.ele[TraceLayout.POINT_OF_COMPASS].setText(c);
	}
	private int showConnectStatistics(Graphics g, int yc, int la) {
		g.setColor(Legend.COLORS[Legend.COLOR_MAP_TEXT]);
		GSMCell cell = null;
		if (cellIDLocationProducer != null || Configuration.getLocationProvider() == Configuration.LOCATIONPROVIDER_SECELL) {
			cell = CellIdProvider.getInstance().obtainCachedCellID();
		}
		Compass compass = null;
		if (Configuration.getCfgBitState(Configuration.CFGBIT_COMPASS_DIRECTION)) {
			compass = CompassProvider.getInstance().obtainCachedCompass();
		}
		if (cell == null) {
			g.drawString("No Cell ID available", 0, yc, Graphics.TOP
					| Graphics.LEFT);
			yc += la;
		} else {
			g.drawString("Cell: MCC=" + cell.mcc + " MNC=" + cell.mnc, 0, yc, Graphics.TOP
					| Graphics.LEFT);
			yc += la;
			g.drawString("LAC=" + cell.lac, 0, yc, Graphics.TOP
					| Graphics.LEFT);
			yc += la;
			g.drawString("cellID=" + cell.cellID, 0, yc, Graphics.TOP
					| Graphics.LEFT);
			yc += la;
		}
		if (compass == null) {
			g.drawString("No compass direction available", 0, yc, Graphics.TOP
					| Graphics.LEFT);
			yc += la;
		} else {
			g.drawString("Compass direction: " + compass.direction, 0, yc, Graphics.TOP
					| Graphics.LEFT);
			yc += la;
		}
		if (statRecord == null) {
			g.drawString("No stats yet", 0, yc, Graphics.TOP
					| Graphics.LEFT);
			return yc+la;
		}
		for (byte i = 0; i < LocationMsgReceiver.SIRF_FAIL_COUNT; i++) {
			g.drawString(statMsg[i] + statRecord[i], 0, yc, Graphics.TOP
					| Graphics.LEFT);
			yc += la;
		}
		g.drawString("BtQual : " + btquality, 0, yc, Graphics.TOP | Graphics.LEFT);
		yc += la;
		g.drawString("Count : " + collected, 0, yc, Graphics.TOP
				| Graphics.LEFT);
		yc += la;
		return yc;
	}
	public void showDestination(PaintContext pc) {
		try {
		if (dest != null) {
			pc.getP().forward(dest.lat, dest.lon, pc.lineP2);
			int x = pc.lineP2.x - imageCollector.xScreenOverscan;
			int y = pc.lineP2.y - imageCollector.yScreenOverscan;
			pc.g.drawImage(pc.images.IMG_DEST, x, y, CENTERPOS);
			pc.g.setColor(Legend.COLORS[Legend.COLOR_DEST_TEXT]);
			if (dest.displayName != null) {
				pc.g.drawString(dest.displayName, x, y+8,
					Graphics.TOP | Graphics.HCENTER);
			}
			pc.g.setColor(Legend.COLORS[Legend.COLOR_DEST_LINE]);
			pc.g.setStrokeStyle(Graphics.SOLID);
			pc.g.drawLine(x, y, pc.getP().getImageCenter().x - imageCollector.xScreenOverscan, 
					pc.getP().getImageCenter().y - imageCollector.yScreenOverscan);
		}
		} catch (Exception e) {
			if (imageCollector == null) {
				logger.silentexception("No ImageCollector", e);
			}
			e.printStackTrace();
		}
	}
	public void showMovement(Graphics g) {
		IntPoint centerP = null;
		try {
			if (imageCollector != null) {
			g.setColor(Legend.COLORS[Legend.COLOR_MAP_CURSOR]);
			centerP = pc.getP().getImageCenter();
			int centerX = centerP.x - imageCollector.xScreenOverscan;
			int centerY = centerP.y - imageCollector.yScreenOverscan;
			int posX, posY;
			if (!gpsRecenter) {
				IntPoint p1 = new IntPoint(0, 0);
				pc.getP().forward((pos.latitude * MoreMath.FAC_DECTORAD),
								  (pos.longitude * MoreMath.FAC_DECTORAD), p1);
				posX = p1.getX()-imageCollector.xScreenOverscan;
				posY = p1.getY()-imageCollector.yScreenOverscan;
			} else {
				posX = centerX;
				posY = centerY;
			}
			g.setColor(Legend.COLORS[Legend.COLOR_MAP_POSINDICATOR]);
			float radc = course * MoreMath.FAC_DECTORAD;
			int px = posX + (int) (Math.sin(radc) * 20);
			int py = posY - (int) (Math.cos(radc) * 20);
			if (!gpsRecenter || gpsRecenterInvalid) {
				g.drawLine(centerX, centerY - 12, centerX, centerY + 12);
				g.drawLine(centerX - 12, centerY, centerX + 12, centerY);
				g.drawArc(centerX - 5, centerY - 5, 10, 10, 0, 360);
			}
			if (! gpsRecenterInvalid) {
				pc.g.drawImage(gpsRecenterStale ? pc.images.IMG_POS_BG_STALE : pc.images.IMG_POS_BG, posX, posY, CENTERPOS);
				g.drawRect(posX - 4, posY - 4, 8, 8);
				g.drawLine(posX, posY, px, py);
			}
			}
		} catch (Exception e) {
			if (imageCollector == null) {
				logger.silentexception("No ImageCollector", e);
			}
			if (centerP == null) {
				logger.silentexception("No centerP", e);
			}
			e.printStackTrace();
		}
	}
	public void showNextDataScreen(int currentScreen) {
		switch (currentScreen)
		{
			case DATASCREEN_TACHO:
				if (guiTrip == null) {
					guiTrip = new GuiTrip(this);
				}
				if (guiTrip != null) {
					guiTrip.show();
				}
				break;
			case DATASCREEN_TRIP:
				if (guiSatellites == null) {
					guiSatellites = new GuiSatellites(this, locationProducer);
				}
				if (guiSatellites != null) {
					guiSatellites.show();
				}
				break;
			case DATASCREEN_SATS:
				this.show();
				break;
			case DATASCREEN_NONE:
			default:
				if (guiTacho == null) {
					guiTacho = new GuiTacho(this);
				}
				if (guiTacho != null) {
					guiTacho.show();
				}
				break;
		}
	}
	public int showMemory(Graphics g, int yc, int la) {
		g.setColor(0);
		g.drawString(Locale.get("trace.Freemem") + runtime.freeMemory(), 0, yc, Graphics.TOP
				| Graphics.LEFT);
		yc += la;
		g.drawString(Locale.get("trace.Totmem") + runtime.totalMemory(), 0, yc, Graphics.TOP
				| Graphics.LEFT);
		yc += la;
		g.drawString(Locale.get("trace.Percent")
				+ (100f * runtime.freeMemory() / runtime.totalMemory()), 0, yc,
				Graphics.TOP | Graphics.LEFT);
		yc += la;
		g.drawString(Locale.get("trace.ThreadsRunning")
				+ Thread.activeCount(), 0, yc,
				Graphics.TOP | Graphics.LEFT);
		yc += la;
		g.drawString(Locale.get("trace.Names") + namesThread.getNameCount(), 0, yc,
				Graphics.TOP | Graphics.LEFT);
		yc += la;
		g.drawString(Locale.get("trace.SingleT") + tileReader.getLivingTilesCount() + "/"
				+ tileReader.getRequestQueueSize(), 0, yc, Graphics.TOP
				| Graphics.LEFT);
		yc += la;
		g.drawString(Locale.get("trace.FileT") + dictReader.getLivingTilesCount() + "/"
				+ dictReader.getRequestQueueSize() + " Map: " + ImageCollector.icDuration + " ms", 0, yc, Graphics.TOP
				| Graphics.LEFT);
		yc += la;
		g.drawString(Locale.get("trace.LastMsg") + lastTitleMsg, 0, yc, Graphics.TOP
				| Graphics.LEFT);
		yc += la;
		g.drawString( Locale.get("trace.at") + lastTitleMsgClock, 0, yc,
				Graphics.TOP | Graphics.LEFT );
		return (yc);
	}
	private void updatePosition() {
		if (pc != null) {
			pc.center = center.copy();
			pc.scale = scale;
			pc.course=course;
			repaint();
			if (locationUpdateListeners != null && !TrackPlayer.isPlaying) {
				synchronized (locationUpdateListeners) {
					for (int i = 0; i < locationUpdateListeners.size(); i++) {
						((LocationUpdateListener)locationUpdateListeners.elementAt(i)).loctionUpdated();
					}
				}
			}
		}
	}
	public synchronized void receivePosition(float lat, float lon, float scale) {
		logger.debug("Now displaying: " + (lat * MoreMath.FAC_RADTODEC) + " | " +
			     (lon * MoreMath.FAC_RADTODEC));
		gpsRecenter = false;
		center.setLatLonRad(lat, lon);
		this.scale = scale;
		updatePosition();
	}
	public synchronized void receiveCompassStatus(int status) {
	}
	public synchronized void receiveCompass(float direction) {
		logger.debug("Got compass reading: " + direction);
		compassDirection = (int) direction;
		compassDeviated = ((int) direction + compassDeviation + 360) % 360;
		if (Configuration.getCfgBitState(Configuration.CFGBIT_COMPASS_DIRECTION) && compassProducer != null && gpsRecenter) {
			updateCourse(compassDeviated);
		}
	}
	public static void updateLastUserActionTime() {
		lastUserActionTime = System.currentTimeMillis();
	}
	public static long getDurationSinceLastUserActionTime() {
		return System.currentTimeMillis() - lastUserActionTime;
	}
	public void updateCourse(int newcourse) {
		coursegps = newcourse;
		if ((newcourse - course)> 180) {
			course = course + 360;
		}
		if ((course-newcourse)> 180) {
			newcourse = newcourse + 360;
		}
		if (Configuration.getCfgBitState(Configuration.CFGBIT_COMPASS_DIRECTION) && compassProducer != null) {
			course = newcourse;
		} else {
			course = course + ((newcourse - course)*1)/4 + 360;
		}
		while (course > 360) {
			course -= 360;
		}
	}
	public synchronized void receivePosition(Position pos) {
		logger.info("New position: " + pos);
		collected++;
		if (Configuration.getAutoRecenterToGpsMilliSecs() !=0 &&
			getDurationSinceLastUserActionTime() > Configuration.getAutoRecenterToGpsMilliSecs()
			&& isShown()
		) {
			gpsRecenter = true;
		}
		if (Configuration.getLocationProvider() == Configuration.LOCATIONPROVIDER_JSR179) {
			if (pos.type == Position.TYPE_GPS_LASTKNOWN) {
				if (this.pos.type == Position.TYPE_CELLID) {
					return;
				}
				gpsRecenterInvalid = false;
				gpsRecenterStale = true;
			}
		}
		this.pos = pos;
		if (pos.type == Position.TYPE_GPS || pos.type == Position.TYPE_CELLID || pos.type == Position.TYPE_MANUAL) {
			gpsRecenterInvalid = false;
			gpsRecenterStale = false;
		}
		if (gpsRecenter) {
			center.setLatLonDeg(pos.latitude, pos.longitude);
			speed = (int) (pos.speed * 3.6f);
			fspeed = pos.speed * 3.6f;
			if (Configuration.getCfgBitState(Configuration.CFGBIT_COMPASS_DIRECTION) && compassProducer != null) {
				updateCourse(compassDeviated);
                       } else if (fspeed >= courseMinSpeed && pos.course != Float.NaN ) {
                               if (thirdPrevCourse != -1) {
                                       updateCourse((int) pos.course);
                               } else if (prevCourse == -1) {
                                       prevCourse = (int) pos.course;
                               } else if (secondPrevCourse == -1) {
                                       if (Math.abs(prevCourse - (int)pos.course) < 30 || Math.abs(prevCourse - (int)pos.course) > 330) {
                                               secondPrevCourse = prevCourse;
                                       }
                                       prevCourse = (int) pos.course;
                               } else {
                                       if (Math.abs(prevCourse - (int)pos.course) < 30 || Math.abs(prevCourse - (int)pos.course) > 330) {
                                               thirdPrevCourse = secondPrevCourse;
                                               updateCourse((int) pos.course);
                                       } else {
                                               prevCourse = (int) pos.course;
                                               secondPrevCourse = -1;
                                       }
                               }
                       } else {
                               prevCourse = -1;
                               secondPrevCourse = -1;
                               thirdPrevCourse = -1;
			}
		}
		if (gpx.isRecordingTrk()) {
			try {
				if ((Configuration.getLocationProvider() == Configuration.LOCATIONPROVIDER_JSR179
				     && pos.type == Position.TYPE_CELLID) || pos.type == Position.TYPE_GPS_LASTKNOWN) {
				} else {
					gpx.addTrkPt(pos);
				}
			} catch (Exception e) {
				receiveMessage(e.getMessage());
			}
		}
		altitude = (int) (pos.altitude);
		if (Configuration.getCfgBitState(Configuration.CFGBIT_AUTOZOOM)
				&& gpsRecenter
				&& (isGpsConnected() || TrackPlayer.isPlaying)
				&& autoZoomed
				&& pc.getP() != null
				&& pos.speed != Float.NaN 
		) {
			final float minimumScale = Configuration.getRealBaseScale() / 1.5f;
			final int minimumSpeed = 20;
			final float maximumScale = Configuration.getRealBaseScale() * 1.5f;
			final int maximumSpeed = 160;
			int speedForScale = speed;
			float newScale = minimumScale + (maximumScale - minimumScale) * (speedForScale - minimumSpeed) / (maximumSpeed - minimumSpeed);
			if (newScale < minimumScale) {
				newScale = minimumScale;
			} else if (newScale > maximumScale) {
				newScale = maximumScale;
			}
			scale = newScale;
		}
		updatePosition();
	}
	public synchronized Position getCurrentPosition() {
		return this.pos;
	}
	public synchronized void receiveMessage(String s) {
		logger.info("Setting title: " + s);
		currentTitleMsg = s;
		synchronized (this) {
			if (setTitleMsgTimeout == 0) {
				currentTitleMsgOpenCount++;
			}
			setTitleMsgTimeout = 3000;
		}
		lastTitleMsgClock = DateTimeTools.getClock(System.currentTimeMillis(), false);
		repaint();
	}
	public void receiveSatellites(Satellite[] sats) {
	}
	public synchronized void alert(String title, String message, int timeout) {
		logger.info("Showing trace alert: " + title + ": " + message);
		if (timeout == Alert.FOREVER) {
			timeout = 10000;
		}
		currentAlertTitle = title;
		currentAlertMessage = message;
		synchronized (this) {
			if (setAlertTimeout == 0) {
				currentAlertsOpenCount++;
			}
			setAlertTimeout = timeout;
		}
		repaint();
	}
	public MIDlet getParent() {
		return parent;
	}
	protected void pointerPressed(int x, int y) {
		updateLastUserActionTime();
		long currTime = System.currentTimeMillis();
		pointerDragged = false;
		pointerDraggedMuch = false;
		pointerActionDone = false;
		centerPointerPressedN = center.copy();
		if (imageCollector != null) {
			panProjection=imageCollector.getCurrentProjection();
			pickPointStart=panProjection.inverse(x,y, pickPointStart);
		} else {
			panProjection = null;
		}
		int touchedElementId = tl.getElementIdAtPointer(x, y);
		if (touchedElementId >= 0 && (!keyboardLocked || tl.getActionIdAtPointer(x, y) == Trace.ICON_MENU)
				&&
			tl.isAnyActionIdAtPointer(x, y)
		) {
			tl.setTouchedElement((LayoutElement) tl.elementAt(touchedElementId));
			repaint();
		}
		if (!keyboardLocked && currTime - pressedPointerTime < DOUBLETAP_MAXDELAY) {
			doubleTap(x, y);
			return;
		}
		pressedPointerTime = currTime;
		Trace.touchX = x;
		Trace.touchY = y;
		if (keyboardLocked) {
			keyPressed(0);
			return;
		}		
		longTapTimerTask = new TimerTask() {
			public void run() {
				if (!pointerActionDone) {
					if (System.currentTimeMillis() - pressedPointerTime >= LONGTAP_DELAY){
						longTap();
					}
				}
			}
		};
		try {
			GpsMid.getTimer().schedule(longTapTimerTask, LONGTAP_DELAY);
		} catch (Exception e) {
			logger.error(Locale.get("trace.NoLongTapTimerTask") + e.toString());
		}
	}
	protected void pointerReleased(int x, int y) {	
		if (longTapTimerTask != null) {
			longTapTimerTask.cancel();
		}
		if (tl.getTouchedElement() != null) {
			tl.clearTouchedElement();
			repaint();
		}
		if (!pointerActionDone && !keyboardLocked) {
			touchReleaseX = x;
			touchReleaseY = y;
			singleTapTimerTask = new TimerTask() {
				public void run() {
					if (!keyboardLocked) {
						singleTap(touchReleaseX, touchReleaseY);
					}
				}
			};
			try {
				GpsMid.getTimer().schedule(singleTapTimerTask, DOUBLETAP_MAXDELAY);
			} catch (Exception e) {
				logger.error(Locale.get("trace.NoSingleTapTimerTask") + e.toString());
			}
			if (pointerDragged) {
				pointerDragged(x , y);
				return;
			}
		}
	}
	protected void pointerDragged (int x, int y) {
		updateLastUserActionTime();
		LayoutElement e = tl.getElementAtPointer(x, y);
		if (tl.getTouchedElement() != e) {
			if (longTapTimerTask != null) {
				longTapTimerTask.cancel();
			}			
			tl.clearTouchedElement();
			repaint();
		}
		if (tl.getElementAtPointer(touchX, touchY) == e && tl.isAnyActionIdAtPointer(x, y)) {
			tl.setTouchedElement(e);
			repaint();
		}
		if (pointerActionDone) {
			return;
		}		
		if (Math.abs(x - Trace.touchX) > 8
				|| 
			Math.abs(y - Trace.touchY) > 8
		) {
			pointerDraggedMuch = true;
			pressedPointerTime = 0; 
		}
		if (tl.getActionIdAtPointer(touchX, touchY) == Trace.ICON_MENU) {
			if ( tl.getActionIdAtPointer(x, y) ==  Trace.ICON_MENU
					&&
				x - touchX > getWidth() / 4
			) {
				commandAction(TOGGLE_KEY_LOCK_CMD);
				pointerActionDone = true;
			}
			return;
		}
		if (keyboardLocked) {
			return;
		}
		pointerDragged = true;
		if (!pointerDraggedMuch && tl.getElementIdAtPointer(touchX, touchY) >= 0) {
			return;
		}
		if (tl.getElementIdAtPointer(touchX, touchY) < 0 && imageCollector != null && panProjection != null) {
			pickPointEnd=panProjection.inverse(x,y, pickPointEnd);
			center.radlat=centerPointerPressedN.radlat-(pickPointEnd.radlat-pickPointStart.radlat);
			center.radlon=centerPointerPressedN.radlon-(pickPointEnd.radlon-pickPointStart.radlon);
			imageCollector.newDataReady();
			gpsRecenter = false;
		}
	}
	private void singleTap(int x, int y) {
		pointerActionDone = true;
		if (tl.getElementIdAtPointer(touchX, touchY) < 0) {							
			if (Configuration.getCfgBitState(Configuration.CFGBIT_MAPTAP_SINGLE)) {
				if (pointerDraggedMuch) {
					return;
				}
				logger.debug("single tap map");
				if (!tl.bigOnScreenButtons) {
					tl.setOnScreenButtonSize(true);
					final long BIGBUTTON_DURATION = 5000;
					bigButtonTimerTask = new TimerTask() {
						public void run() {
							if (System.currentTimeMillis() - lastUserActionTime > BIGBUTTON_DURATION ) {
								tl.setOnScreenButtonSize(false);
								requestRedraw();						
								bigButtonTimerTask.cancel();
							}
						}
					};
					try {
						GpsMid.getTimer().schedule(bigButtonTimerTask, BIGBUTTON_DURATION, 500);
					} catch (Exception e) {
						logger.error("Error scheduling bigButtonTimerTask: " + e.toString());
					}
				}
			}
			repaint();
		} else if (tl.getElementIdAtPointer(x, y) == tl.getElementIdAtPointer(touchX, touchY)) {
			tl.clearTouchedElement();
			int actionId = tl.getActionIdAtPointer(x, y);
			if (actionId > 0) {
				logger.debug("single tap button: " + actionId + " x: " + touchX + " y: " + touchY);
				if (System.currentTimeMillis() < (lastBackLightOnTime + 2500)) {
					if (actionId == ZOOM_IN_CMD) {
						actionId = PAN_RIGHT2_CMD;
					} else if (actionId == ZOOM_OUT_CMD) {
						actionId = PAN_LEFT2_CMD;
					}
				} else if (manualRotationMode) {
					if (actionId == ZOOM_IN_CMD) {
						actionId = PAN_LEFT2_CMD;
					} else if (actionId == ZOOM_OUT_CMD) {
						actionId = PAN_RIGHT2_CMD;
					}
				} else if (TrackPlayer.isPlaying) {
					if (actionId == ZOOM_IN_CMD) {
						actionId = PAN_RIGHT2_CMD;
					} else if (actionId == ZOOM_OUT_CMD) {
						actionId = PAN_LEFT2_CMD;
					}
				}
				commandAction(actionId);
				repaint();
			}
		}
	}
	private void doubleTap(int x, int y) {
		if (tl.getElementIdAtPointer(touchX, touchY) < 0) {
			if (Configuration.getCfgBitState(Configuration.CFGBIT_MAPTAP_DOUBLE)) {
				if (singleTapTimerTask != null) {
					singleTapTimerTask.cancel();
				}
				if (pointerDraggedMuch) {
					return;
				}
				logger.debug("double tap map");
				pointerActionDone = true;
				commandAction(ZOOM_IN_CMD);
			}
			repaint();
			return;
		} else if (tl.getTouchedElement() == tl.getElementAtPointer(x, y) ){
			int actionId = tl.getActionIdDoubleAtPointer(x, y);
			logger.debug("double tap button: " + actionId + " x: " + x + " y: " + x);
			if (actionId > 0) {
				if (singleTapTimerTask != null) {
					singleTapTimerTask.cancel();
				}
				pointerActionDone = true;
				commandAction(actionId);
				tl.clearTouchedElement();
				repaint();
				return;
			} else {
				singleTap(x, y);
			}
		}
	}
	private void longTap() {
		if (tl.getElementIdAtPointer(touchX, touchY) < 0 && panProjection != null) {							
			if (!pointerDraggedMuch && Configuration.getCfgBitState(Configuration.CFGBIT_MAPTAP_LONG)) {
				pointerActionDone = true;
				logger.debug("long tap map");										
				pickPointEnd=panProjection.inverse(touchX,touchY, pickPointEnd);
				center.radlat=centerPointerPressedN.radlat-(pickPointEnd.radlat-pickPointStart.radlat);
				center.radlon=centerPointerPressedN.radlon-(pickPointEnd.radlon-pickPointStart.radlon);
				Position oPos = new Position(center.radlat, center.radlon,
							     0.0f, 0.0f, 0.0f, 0, 0);
				imageCollector.newDataReady();
				gpsRecenter = false;
				commandAction(ONLINE_INFO_CMD);
			}
			return;
		} else {
			int actionId = tl.getActionIdLongAtPointer(touchX, touchY);
			if (actionId > 0 && tl.getElementAtPointer(touchX, touchY) == tl.getTouchedElement()) {
				pointerActionDone = true;
				logger.debug("long tap button: " + actionId + " x: " + touchX + " y: " + touchY);
				commandAction(actionId);
				repaint();
			}
		}
		tl.clearTouchedElement();
	}
	public Command getDataScreenCommand() {
		return CMDS[DATASCREEN_CMD];
	}
	public Tile getDict(byte zl) {
		return tiles[zl];
	}
	public void setDict(Tile dict, byte zl) {
		tiles[zl] = dict;
		if (zl == 0) {
			Configuration.getStartupPos(center);
			if (center.radlat == 0.0f && center.radlon == 0.0f) {
				dict.getCenter(center);
			}
			if (Configuration.getCfgBitState(Configuration.CFGBIT_SAVED_DESTPOS_VALID)) {
				Node destNode = new Node();
				Configuration.getDestPos(destNode);
				setDestination(new RoutePositionMark(destNode.radlat, destNode.radlon));
			}
			if (pc != null) {
				pc.center = center.copy();
				pc.scale = scale;
				pc.course = course;
			}
		}
		updatePosition();
	}
	public void receiveStatistics(int[] statRecord, byte quality) {
		this.btquality = quality;
		this.statRecord = statRecord;
		repaint();
	}
	public synchronized void locationDecoderEnd() {
		logger.info("enter locationDecoderEnd");
		if (Configuration.getCfgBitState(Configuration.CFGBIT_SND_DISCONNECT)) {
			if (Legend.isValid) {
				GpsMid.mNoiseMaker.playSound("DISCONNECT");
			}
		}
		removeCommand(CMDS[DISCONNECT_GPS_CMD]);
		if (locationProducer == null) {
			logger.info("leave locationDecoderEnd no producer");
			return;
		}
		locationProducer = null;
		notify();
		addCommand(CMDS[CONNECT_GPS_CMD]);
		logger.info("end locationDecoderEnd");
	}
	public void receiveStatus(byte status, int satsReceived) {
		solution = status;
		solutionStr = LocationMsgReceiverList.getCurrentStatusString(status, satsReceived);
		repaint();
	}
	public String getName(int idx) {
		if (idx < 0) {
			return null;
		}
		return namesThread.getName(idx);
	}
	public String getUrl(int idx) {
		if (idx < 0) {
			return null;
		}
		return urlsThread.getUrl(idx);
	}
	public Vector fulltextSearch (String snippet, CancelMonitorInterface cmi) {
		return namesThread.fulltextSearch(snippet, cmi);
	}
	public void requestRedraw() {
		repaint();
	}
	public void newDataReady() {
		if (imageCollector != null) {
			imageCollector.newDataReady();
		}
	}
	public void show() {
		Legend.freeDrawnWayAndAreaSearchImages();
		GpsMid.getInstance().show(this);
		setFullScreenMode(Configuration.getCfgBitState(Configuration.CFGBIT_FULLSCREEN));
		updateLastUserActionTime();
		repaint();
	}
	public void recreateTraceLayout() {
		tl = new TraceLayout(0, 0, getWidth(), getHeight());
	}
	public void resetSize() {
		sizeChanged(getWidth(), getHeight());
	}
	public void locationDecoderEnd(String msg) {
		receiveMessage(msg);
		locationDecoderEnd();
	}
	public PositionMark getDestination() {
		return dest;
	}
	public void setDestination(RoutePositionMark dest) {
		endRouting();
		this.dest = dest;
		pc.dest = dest;
		if (dest != null) {
			logger.info("Setting destination to " + dest.toString());
			if (! Configuration.getCfgBitState(Configuration.CFGBIT_ICONMENUS_ROUTING_OPTIMIZED)) {
				commandAction(SHOW_DEST_CMD);
			}
			if (Configuration.getCfgBitState(Configuration.CFGBIT_AUTOSAVE_DESTPOS)) {
				Configuration.setDestPos(new Node(dest.lat, dest.lon, true));
				Configuration.setCfgBitSavedState(Configuration.CFGBIT_SAVED_DESTPOS_VALID, true);
			}
			movedAwayFromDest = false;
		} else {
			Configuration.setCfgBitSavedState(Configuration.CFGBIT_SAVED_DESTPOS_VALID, false);
			logger.info("Setting destination to null");
		}
	}
	public void endRouting() {
		RouteInstructions.initialRecalcDone = false;
		RouteInstructions.icCountOffRouteDetected = 0;
		RouteInstructions.routeInstructionsHeight = 0;
		RouteInstructions.abortRouteLineProduction();
		setRoute(null);
		setRouteNodes(null);
	}
	public void setRoute(Vector route) {
		synchronized(this) {
			this.route = route;
		}
		if (this.route != null) {
			RouteInstructions.resetOffRoute(this.route, center);
			if (ri == null) {
				ri = new RouteInstructions(this);
			}
			if (Configuration.getContinueMapWhileRouteing() == Configuration.continueMap_At_Route_Line_Creation) {
				resumeImageCollectorAfterRouteCalc();
			}
			ri.newRoute(this.route);
			oldRecalculationTime = System.currentTimeMillis();
		}
		resumeImageCollectorAfterRouteCalc();
		routeCalc=false;
		routeEngine=null;
	}
	private void resumeImageCollectorAfterRouteCalc() {
		try {
			if (imageCollector == null) {
				startImageCollector();
				imageCollector.resume();
			} else if (imageCollector != null) {
				imageCollector.newDataReady();
			}
			repaint();
		} catch (Exception e) {
			logger.exception(Locale.get("trace.InTraceResumeImageCollector"), e);
		}
	}
	public void dropCache() {
		tileReader.dropCache();
		dictReader.dropCache();
		System.gc();
		namesThread.dropCache();
		urlsThread.dropCache(); 
		System.gc();
		if (gpx != null) {
			gpx.dropCache();
		}
	}
	public QueueDataReader getDataReader() {
		return tileReader;
	}
	public QueueDictReader getDictReader() {
		return dictReader;
	}
	public Vector getRouteNodes() {
		return routeNodes;
	}
	public void setRouteNodes(Vector routeNodes) {
		this.routeNodes = routeNodes;
	}
	protected void hideNotify() {
		logger.debug("Hide notify has been called, screen will no longer be updated");
		if (imageCollector != null) {
			imageCollector.suspend();
		}
	}
	protected void showNotify() {
		logger.debug("Show notify has been called, screen will be updated again");
		if (imageCollector != null) {
			imageCollector.resume();
			imageCollector.newDataReady();
		}
	}
	public Vector getRoute() {
		return route;
	}
	public void actionCompleted() {
		boolean reAddCommands = true;
		if (reAddCommands && Configuration.getCfgBitState(Configuration.CFGBIT_FULLSCREEN)) {
			addAllCommands();
		}
	}
	public void showIconMenu() {
		if (traceIconMenu == null) {
			traceIconMenu = new TraceIconMenu(this, this);
		}
		traceIconMenu.show();
	}
	public static void uncacheIconMenu() {
		if (traceIconMenu != null) {
			logger.trace("uncaching TraceIconMenu");
		}
		traceIconMenu = null;
	}
	public void recreateAndShowIconMenu() {
		uncacheIconMenu();
		showIconMenu();
	}
	public void performIconAction(int actionId) {
		updateLastUserActionTime();
		if (routeCalc || GpsMid.getInstance().needsFreeingMemory()) {
			logger.info("low mem: Uncaching traceIconMenu");
			uncacheIconMenu();
		}
		if (actionId != IconActionPerformer.BACK_ACTIONID) {
			commandAction(actionId);
		}
	}
	public static String showDistance(int meters) {
		return showDistance(meters, DISTANCE_GENERIC);
	}
	public static String showDistance(int meters, int type) {
		if (Configuration.getCfgBitState(Configuration.CFGBIT_METRIC)) {
			if (type == DISTANCE_UNKNOWN) {
				return "???m";
			}
			if (Configuration.getCfgBitState(Configuration.CFGBIT_DISTANCE_VIEW) && (type != DISTANCE_ALTITUDE)) {
				if (meters >= 10000) {
					return meters / 1000 + "km";
				} else if (meters < 1000) {
					return meters + "m";
				} else {
					return meters / 1000 + "." + (meters % 1000) / 100 + "km";
				}
			} else {
				return meters + "m";
			}
		} else {
			if (type == DISTANCE_UNKNOWN) {
				return "???yd";
			} else if (type == DISTANCE_ALTITUDE) {
				return Integer.toString((int)(meters / 0.30480 + 0.5)) + "ft";
			} else {
				return Integer.toString((int)(meters / 0.9144 + 0.5)) + "yd";
			}
		}
	}
}
