package tim.prune.gui;

import java.awt.Toolkit;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JToolBar;
import javax.swing.KeyStroke;

import tim.prune.App;
import tim.prune.DataSubscriber;
import tim.prune.FunctionLibrary;
import tim.prune.GenericFunction;
import tim.prune.I18nManager;
import tim.prune.UpdateMessageBroker;
import tim.prune.config.Config;
import tim.prune.data.AudioClip;
import tim.prune.data.DataPoint;
import tim.prune.data.Field;
import tim.prune.data.GeocacheCode;
import tim.prune.data.Photo;
import tim.prune.data.RecentFile;
import tim.prune.data.RecentFileList;
import tim.prune.data.Selection;
import tim.prune.data.Track;
import tim.prune.data.TrackInfo;
import tim.prune.function.AverageSelection;
import tim.prune.function.ChooseSingleParameter;
import tim.prune.function.CutAndMoveFunction;
import tim.prune.function.DeleteCurrentPoint;
import tim.prune.function.InterpolateTimestamps;
import tim.prune.function.PasteCoordinateList;
import tim.prune.function.PasteCoordinates;
import tim.prune.function.PlusCodeFunction;
import tim.prune.function.ProjectRange;
import tim.prune.function.ReverseSelectedRange;
import tim.prune.function.SelectExtremePoint;
import tim.prune.function.browser.OpenCachePageFunction;
import tim.prune.function.browser.UrlGenerator;
import tim.prune.function.browser.WebMapFunction;
import tim.prune.function.comparesegments.CompareSegmentsFunction;
import tim.prune.function.edit.PointEditor;
import tim.prune.function.filesleuth.StartFindFilesFunction;
import tim.prune.function.search.SearchOpenCachingDeFunction;
import tim.prune.function.segments.MergeSegmentsFunction;
import tim.prune.function.settings.SaveConfig;
import tim.prune.function.srtm.ConfigureSrtmSources;
import tim.prune.threedee.WindowFactory;

/**
 * Class to manage the menu bar and toolbar,
 * including enabling and disabling the items
 */
public class MenuManager implements DataSubscriber
{
	private final App _app;
	private final Track _track;
	private final Selection _selection;
	private final boolean _threeDAvailable;

	// Menu items which need enabling/disabling
	private JMenuItem _sendGpsItem = null;
	private JMenuItem _saveItem = null;
	private JMenuItem _exportGpxItem = null;
	private JMenuItem _exportKmlItem = null;
	private JMenuItem _exportPovItem = null;
	private JMenuItem _exportImageItem = null;
	private JMenu     _recentFileMenu = null;
	private JMenuItem _undoItem = null;
	private JMenuItem _clearUndoItem = null;
	private JMenuItem _redoItem = null;
	private JMenuItem _editPointItem = null;
	private JMenuItem _editWaypointNameItem = null;
	private JMenuItem _togglePointSegmentItem = null;
	private JMenuItem _deletePointItem = null;
	private JMenuItem _truncatePointCoordsItem = null;
	private JMenuItem _deleteRangeItem = null;
	private JMenuItem _cropTrackItem = null;
	private JMenuItem _compressItem = null;
	private JMenuItem _markRectangleInsideItem = null;
	private JMenuItem _markRectangleOutsideItem = null;
	private JMenuItem _deleteMarkedPointsItem = null;
	private JMenuItem _deleteByDateItem = null;
	private JMenuItem _interpolateItem = null;
	private JMenuItem _interpolateTimestampsItem = null;
	private JMenuItem _averageItem = null;
	private JMenuItem _selectAllItem = null;
	private JMenuItem _selectNoneItem = null;
	private JMenuItem _selectSegmentItem = null;
	private JMenuItem _selectStartItem = null;
	private JMenuItem _selectEndItem = null;
	private JMenuItem _findWaypointItem = null;
	private JMenu     _gotoPointMenu = null;
	private JMenuItem _highestPointItem = null;
	private JMenuItem _lowestPointItem = null;
	private JMenuItem _fastestPointItem = null;
	private JMenuItem _duplicatePointItem = null;
	private JMenuItem _projectPointItem = null;
	private JMenuItem _projectCircleItem = null;
	private JMenuItem _reverseItem = null;
	private JMenuItem _addTimeOffsetItem = null;
	private JMenuItem _addAltitudeOffsetItem = null;
	private JMenuItem _projectRangeItem = null;
	private JMenuItem _mergeSegmentsItem = null;
	private JMenuItem _rearrangeWaypointsItem = null;
	private JMenuItem _dedupeWaypointsItem = null;
	private JMenuItem _splitSegmentsItem = null;
	private JMenuItem _sewSegmentsItem = null;
	private JMenuItem _createMarkerWaypointsItem = null;
	private JMenuItem _cutAndMoveItem = null;
	private JMenuItem _deleteFieldValuesItem = null;
	private JCheckBoxMenuItem _mapCheckbox = null;
	private JMenuItem _show3dItem = null;
	private JMenu     _browserMapMenu = null;
	private JMenuItem _routingGraphHopperItem = null;
	private JMenuItem _chartItem = null;
	private JMenuItem _lookupSrtmItem = null;
	private JMenuItem _nearbyWikipediaItem = null;
	private JMenuItem _nearbyOsmPoiItem = null;
	private JMenuItem _showPeakfinderItem = null;
	private JMenuItem _showGeohackItem = null;
	private JMenuItem _searchOpencachingDeItem = null;
	private JMenuItem _openCachePageItem = null;
	private JMenuItem _downloadOsmItem = null;
	private JMenuItem _getWeatherItem = null;
	private JMenuItem _distanceItem = null;
	private JMenuItem _viewFullDetailsItem = null;
	private JMenuItem _estimateTimeItem = null;
	private JMenuItem _learnEstimationParams = null;
	private JMenuItem _compareSegmentsItem = null;
	private JMenuItem _autoplayTrack = null;
	private JMenuItem _saveExifItem = null;
	private JMenuItem _photoPopupItem = null;
	private JMenuItem _selectNoPhotoItem = null;
	private JMenuItem _connectPhotoItem = null;
	private JMenuItem _removePhotoItem = null;
	private JMenuItem _disconnectPhotoItem = null;
	private JMenuItem _correlatePhotosItem = null;
	private JMenuItem _rearrangePhotosItem = null;
	private JMenuItem _rotatePhotoLeft = null;
	private JMenuItem _rotatePhotoRight = null;
	private JMenuItem _ignoreExifThumb = null;
	private JMenuItem _connectAudioItem = null;
	private JMenuItem _disconnectAudioItem = null;
	private JMenuItem _removeAudioItem = null;
	private JMenuItem _correlateAudiosItem = null;
	private JMenuItem _selectNoAudioItem = null;
	private JCheckBoxMenuItem _onlineCheckbox = null;
	private JCheckBoxMenuItem _autosaveSettingsCheckbox = null;

	// Toolbar buttons which need enabling/disabling
	private JButton _saveButton = null;
	private JButton _undoButton = null;
	private JButton _redoButton = null;
	private JButton _editPointButton = null;
	private JButton _viewInfoButton = null;
	private JButton _deletePointButton = null;
	private JButton _deleteRangeButton = null;
	private JButton _cutAndMoveButton = null;
	private JButton _selectStartButton = null;
	private JButton _selectEndButton = null;
	private JButton _connectButton = null;
	private JButton _threeDeeButton = null;

	/** Array of key events */
	private static final int[] KEY_EVENTS = {
		KeyEvent.VK_A, KeyEvent.VK_B, KeyEvent.VK_C, KeyEvent.VK_D, KeyEvent.VK_E,
		KeyEvent.VK_F, KeyEvent.VK_G, KeyEvent.VK_H, KeyEvent.VK_I, KeyEvent.VK_J,
		KeyEvent.VK_K, KeyEvent.VK_L, KeyEvent.VK_M, KeyEvent.VK_N, KeyEvent.VK_O,
		KeyEvent.VK_P, KeyEvent.VK_Q, KeyEvent.VK_R, KeyEvent.VK_S, KeyEvent.VK_T,
		KeyEvent.VK_U, KeyEvent.VK_V, KeyEvent.VK_W, KeyEvent.VK_X, KeyEvent.VK_Y, KeyEvent.VK_Z};


	/**
	 * Constructor
	 * @param inApp application to call on menu actions
	 * @param inTrackInfo track info object
	 */
	public MenuManager(App inApp, TrackInfo inTrackInfo)
	{
		_app = inApp;
		_track = inTrackInfo.getTrack();
		_selection = inTrackInfo.getSelection();
		_threeDAvailable = WindowFactory.isJava3dEnabled();
	}

	private Config getConfig() {
		return _app.getConfig();
	}

	/**
	 * @return a JMenuBar containing all menu items
	 */
	public JMenuBar createMenuBar()
	{
		JMenuBar menubar = new JMenuBar();
		JMenu fileMenu = new JMenu(I18nManager.getText("menu.file"));
		setAltKey(fileMenu, "altkey.menu.file");
		// Open file
		JMenuItem openMenuItem = new JMenuItem(I18nManager.getText("function.open"));
		setShortcut(openMenuItem, "shortcut.menu.file.open");
		openMenuItem.addActionListener(e -> _app.openFile());
		fileMenu.add(openMenuItem);
		// import through gpsbabel
		JMenuItem importBabelItem = makeMenuItem(FunctionLibrary.FUNCTION_IMPORTBABEL);
		fileMenu.add(importBabelItem);
		// Add photos
		JMenuItem addPhotosMenuItem = new JMenuItem(I18nManager.getText("menu.file.addphotos"));
		addPhotosMenuItem.addActionListener(e -> _app.addPhotos());
		fileMenu.add(addPhotosMenuItem);
		// Add audio clips
		JMenuItem addAudioMenuItem = makeMenuItem(FunctionLibrary.FUNCTION_LOAD_AUDIO);
		fileMenu.add(addAudioMenuItem);
		// Find files
		JMenuItem findFileItem = makeMenuItem(new StartFindFilesFunction(_app));
		fileMenu.add(findFileItem);
		// recent files
		_recentFileMenu = new JMenu(I18nManager.getText("menu.file.recentfiles"));
		_recentFileMenu.setEnabled(false);
		fileMenu.add(_recentFileMenu);
		fileMenu.addSeparator();
		// Load from GPS
		JMenuItem loadFromGpsMenuItem = makeMenuItem(FunctionLibrary.FUNCTION_GPSLOAD);
		setShortcut(loadFromGpsMenuItem, "shortcut.menu.file.load");
		fileMenu.add(loadFromGpsMenuItem);
		// Send to GPS
		_sendGpsItem = makeMenuItem(FunctionLibrary.FUNCTION_GPSSAVE, false);
		fileMenu.add(_sendGpsItem);
		fileMenu.addSeparator();
		// Save
		_saveItem = new JMenuItem(I18nManager.getText("menu.file.save"), KeyEvent.VK_S);
		setShortcut(_saveItem, "shortcut.menu.file.save");
		_saveItem.addActionListener(e -> _app.saveFile());
		_saveItem.setEnabled(false);
		fileMenu.add(_saveItem);
		// Export - Gpx
		_exportGpxItem = makeMenuItem(FunctionLibrary.FUNCTION_GPXEXPORT, false);
		fileMenu.add(_exportGpxItem);
		// Kml
		_exportKmlItem = makeMenuItem(FunctionLibrary.FUNCTION_KMLEXPORT, false);
		fileMenu.add(_exportKmlItem);
		// Pov
		_exportPovItem = makeMenuItem(FunctionLibrary.FUNCTION_POVEXPORT, false);
		fileMenu.add(_exportPovItem);
		// Image
		_exportImageItem = makeMenuItem(FunctionLibrary.FUNCTION_IMAGEEXPORT, false);
		fileMenu.add(_exportImageItem);
		fileMenu.addSeparator();
		// Exit
		JMenuItem exitMenuItem = new JMenuItem(I18nManager.getText("menu.file.exit"));
		exitMenuItem.addActionListener(e -> _app.exit());
		fileMenu.add(exitMenuItem);
		menubar.add(fileMenu);

		////////////////////////////////////////////////////
		// Online menu
		JMenu onlineMenu = new JMenu(I18nManager.getText("menu.online"));
		setAltKey(onlineMenu, "altkey.menu.online");
		// SRTM
		_lookupSrtmItem = makeMenuItem(FunctionLibrary.FUNCTION_LOOKUP_SRTM, false);
		onlineMenu.add(_lookupSrtmItem);
		JMenuItem configureSrtmItem = makeMenuItem(new ConfigureSrtmSources(_app), true);
		onlineMenu.add(configureSrtmItem);

		onlineMenu.addSeparator();
		// browser submenu
		_browserMapMenu = new JMenu(I18nManager.getText("menu.view.browser"));
		_browserMapMenu.setEnabled(false);
		JMenuItem googleMapsItem = makeMenuItem(new WebMapFunction(_app, UrlGenerator.WebService.MAP_SOURCE_GOOGLE, "menu.view.browser.google"));
		_browserMapMenu.add(googleMapsItem);
		JMenuItem openMapsItem = makeMenuItem(new WebMapFunction(_app, UrlGenerator.WebService.MAP_SOURCE_OSM, "menu.view.browser.openstreetmap"));
		_browserMapMenu.add(openMapsItem);
		JMenuItem mapquestMapsItem = makeMenuItem(new WebMapFunction(_app, UrlGenerator.WebService.MAP_SOURCE_MAPQUEST, "menu.view.browser.mapquest"));
		_browserMapMenu.add(mapquestMapsItem);
		JMenuItem bingMapsItem = makeMenuItem(new WebMapFunction(_app, UrlGenerator.WebService.MAP_SOURCE_BING, "menu.view.browser.bing"));
		_browserMapMenu.add(bingMapsItem);
		JMenuItem mapillaryItem = makeMenuItem(new WebMapFunction(_app, UrlGenerator.WebService.MAP_SOURCE_MAPILLARY, "menu.view.browser.mapillary"));
		_browserMapMenu.add(mapillaryItem);
		_routingGraphHopperItem = makeMenuItem(new WebMapFunction(_app, UrlGenerator.WebService.MAP_SOURCE_GRAPHHOPPER, "menu.view.browser.graphhopper"));
		_browserMapMenu.add(_routingGraphHopperItem);
		onlineMenu.add(_browserMapMenu);
		// wikipedia
		_nearbyWikipediaItem = makeMenuItem(FunctionLibrary.FUNCTION_NEARBY_WIKIPEDIA, false);
		onlineMenu.add(_nearbyWikipediaItem);
		JMenuItem searchWikipediaNamesItem = makeMenuItem(FunctionLibrary.FUNCTION_SEARCH_WIKIPEDIA);
		onlineMenu.add(searchWikipediaNamesItem);
		_nearbyOsmPoiItem = makeMenuItem(FunctionLibrary.FUNCTION_SEARCH_OSMPOIS);
		onlineMenu.add(_nearbyOsmPoiItem);
		_showPeakfinderItem = makeMenuItem(new WebMapFunction(_app, UrlGenerator.WebService.MAP_SOURCE_PEAKFINDER, "webservice.peakfinder"), false);
		onlineMenu.add(_showPeakfinderItem);
		_showGeohackItem = makeMenuItem(new WebMapFunction(_app, UrlGenerator.WebService.MAP_SOURCE_GEOHACK, "webservice.geohack"), false);
		onlineMenu.add(_showGeohackItem);

		onlineMenu.addSeparator();
		_searchOpencachingDeItem = makeMenuItem(new SearchOpenCachingDeFunction(_app), false);
		onlineMenu.add(_searchOpencachingDeItem);
		_openCachePageItem = makeMenuItem(new OpenCachePageFunction(_app), false);
		onlineMenu.add(_openCachePageItem);
		_downloadOsmItem = makeMenuItem(FunctionLibrary.FUNCTION_DOWNLOAD_OSM, false);
		onlineMenu.add(_downloadOsmItem);
		_getWeatherItem = makeMenuItem(FunctionLibrary.FUNCTION_GET_WEATHER_FORECAST, false);
		onlineMenu.add(_getWeatherItem);
		menubar.add(onlineMenu);

		////////////////////////////////////////////////////
		// Track menu
		JMenu trackMenu = new JMenu(I18nManager.getText("menu.track"));
		setAltKey(trackMenu, "altkey.menu.track");
		_undoItem = new JMenuItem(I18nManager.getText("menu.track.undo"));
		setShortcut(_undoItem, "shortcut.menu.track.undo");
		_undoItem.addActionListener(e -> _app.beginUndo());
		_undoItem.setEnabled(false);
		trackMenu.add(_undoItem);
		_clearUndoItem = new JMenuItem(I18nManager.getText("menu.track.clearundo"));
		_clearUndoItem.addActionListener(e -> _app.clearUndo());
		_clearUndoItem.setEnabled(false);
		trackMenu.add(_clearUndoItem);
		_redoItem = new JMenuItem(I18nManager.getText("menu.track.redo"));
		_redoItem.addActionListener(e -> _app.beginRedo());
		_redoItem.setEnabled(false);
		trackMenu.add(_redoItem);
		trackMenu.addSeparator();
		_compressItem = makeMenuItem(FunctionLibrary.FUNCTION_COMPRESS, false);
		setShortcut(_compressItem, "shortcut.menu.track.compress");
		trackMenu.add(_compressItem);
		_markRectangleInsideItem = new JMenuItem(I18nManager.getText("menu.track.markinsiderectangle"));
		_markRectangleInsideItem.addActionListener(e -> {
			_app.setCurrentMode(App.AppMode.DRAWRECT_INSIDE);
			UpdateMessageBroker.informSubscribers();
		});
		_markRectangleInsideItem.setEnabled(false);
		trackMenu.add(_markRectangleInsideItem);
		_markRectangleOutsideItem = new JMenuItem(I18nManager.getText("menu.track.markoutsiderectangle"));
		_markRectangleOutsideItem.addActionListener(e -> {
			_app.setCurrentMode(App.AppMode.DRAWRECT_OUTSIDE);
			UpdateMessageBroker.informSubscribers();
		});
		_markRectangleOutsideItem.setEnabled(false);
		trackMenu.add(_markRectangleOutsideItem);
		_deleteMarkedPointsItem = makeMenuItem(FunctionLibrary.FUNCTION_DELETE_MARKED_POINTS, false);
		trackMenu.add(_deleteMarkedPointsItem);
		_deleteByDateItem = makeMenuItem(FunctionLibrary.FUNCTION_DELETE_BY_DATE, false);
		trackMenu.add(_deleteByDateItem);
		trackMenu.addSeparator();
		// Rearrange waypoints
		_rearrangeWaypointsItem = makeMenuItem(FunctionLibrary.FUNCTION_REARRANGE_WAYPOINTS, false);
		trackMenu.add(_rearrangeWaypointsItem);
		_dedupeWaypointsItem = makeMenuItem(FunctionLibrary.FUNCTION_DEDUPE_WAYPOINTS, false);
		trackMenu.add(_dedupeWaypointsItem);
		// Split track segments
		_splitSegmentsItem = makeMenuItem(FunctionLibrary.FUNCTION_SPLIT_SEGMENTS, false);
		trackMenu.add(_splitSegmentsItem);
		// Sew track segments
		_sewSegmentsItem = makeMenuItem(FunctionLibrary.FUNCTION_SEW_SEGMENTS, false);
		trackMenu.add(_sewSegmentsItem);
		// Create marker waypoints
		_createMarkerWaypointsItem = makeMenuItem(FunctionLibrary.FUNCTION_CREATE_MARKER_WAYPOINTS, false);
		trackMenu.add(_createMarkerWaypointsItem);
		trackMenu.addSeparator();
		_learnEstimationParams = makeMenuItem(FunctionLibrary.FUNCTION_LEARN_ESTIMATION_PARAMS, false);
		trackMenu.add(_learnEstimationParams);
		menubar.add(trackMenu);

		// Range menu
		JMenu rangeMenu = new JMenu(I18nManager.getText("menu.range"));
		setAltKey(rangeMenu, "altkey.menu.range");
		_selectAllItem = new JMenuItem(I18nManager.getText("menu.range.all"));
		setShortcut(_selectAllItem, "shortcut.menu.range.all");
		_selectAllItem.setEnabled(false);
		_selectAllItem.addActionListener(e -> _selection.selectRange(0, _track.getNumPoints()-1));
		rangeMenu.add(_selectAllItem);
		_selectNoneItem = new JMenuItem(I18nManager.getText("menu.range.none"));
		_selectNoneItem.setEnabled(false);
		_selectNoneItem.addActionListener(e -> _app.selectNone());
		rangeMenu.add(_selectNoneItem);
		_selectSegmentItem = makeMenuItem(FunctionLibrary.FUNCTION_SELECT_SEGMENT);
		rangeMenu.add(_selectSegmentItem);
		rangeMenu.addSeparator();
		_selectStartItem = new JMenuItem(I18nManager.getText("menu.range.start"));
		_selectStartItem.setEnabled(false);
		_selectStartItem.addActionListener(e -> _selection.selectRangeStart());
		rangeMenu.add(_selectStartItem);
		_selectEndItem = new JMenuItem(I18nManager.getText("menu.range.end"));
		_selectEndItem.setEnabled(false);
		_selectEndItem.addActionListener(e -> _selection.selectRangeEnd());
		rangeMenu.add(_selectEndItem);
		rangeMenu.addSeparator();
		_deleteRangeItem = makeMenuItem(FunctionLibrary.FUNCTION_DELETE_RANGE, false);
		rangeMenu.add(_deleteRangeItem);
		_cropTrackItem = makeMenuItem(FunctionLibrary.FUNCTION_CROP_TRACK, false);
		rangeMenu.add(_cropTrackItem);
		_reverseItem = makeMenuItem(new ReverseSelectedRange(_app), false);
		rangeMenu.add(_reverseItem);
		_addTimeOffsetItem = makeMenuItem(FunctionLibrary.FUNCTION_ADD_TIME_OFFSET, false);
		rangeMenu.add(_addTimeOffsetItem);
		_addAltitudeOffsetItem = makeMenuItem(FunctionLibrary.FUNCTION_ADD_ALTITUDE_OFFSET, false);
		rangeMenu.add(_addAltitudeOffsetItem);
		_projectRangeItem = makeMenuItem(new ProjectRange(_app), false);
		rangeMenu.add(_projectRangeItem);
		_mergeSegmentsItem = makeMenuItem(new MergeSegmentsFunction(_app), false);
		rangeMenu.add(_mergeSegmentsItem);
		_deleteFieldValuesItem = makeMenuItem(FunctionLibrary.FUNCTION_DELETE_FIELD_VALUES, false);
		rangeMenu.add(_deleteFieldValuesItem);
		_interpolateTimestampsItem = makeMenuItem(new InterpolateTimestamps(_app), false);
		rangeMenu.add(_interpolateTimestampsItem);
		rangeMenu.addSeparator();
		_interpolateItem = makeMenuItem(new ChooseSingleParameter(_app, FunctionLibrary.FUNCTION_INTERPOLATE), false);
		rangeMenu.add(_interpolateItem);
		_averageItem = makeMenuItem(new AverageSelection(_app), false);
		_averageItem.setEnabled(false);
		rangeMenu.add(_averageItem);
		_cutAndMoveItem = makeMenuItem(new CutAndMoveFunction(_app), false);
		rangeMenu.add(_cutAndMoveItem);
		menubar.add(rangeMenu);

		// Point menu
		JMenu pointMenu = new JMenu(I18nManager.getText("menu.point"));
		setAltKey(pointMenu, "altkey.menu.point");
		_editPointItem = makeMenuItem(new PointEditor(_app), false);
		setShortcut(_editPointItem, "shortcut.menu.point.edit");
		pointMenu.add(_editPointItem);
		_editWaypointNameItem = makeMenuItem(FunctionLibrary.FUNCTION_EDIT_WAYPOINT_NAME, false);
		pointMenu.add(_editWaypointNameItem);
		_togglePointSegmentItem = makeMenuItem(FunctionLibrary.FUNCTION_TOGGLE_POINT_SEGMENT, false);
		pointMenu.add(_togglePointSegmentItem);
		_deletePointItem = makeMenuItem(new DeleteCurrentPoint(_app), false);
		_deletePointItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0));
		pointMenu.add(_deletePointItem);
		_truncatePointCoordsItem = makeMenuItem(FunctionLibrary.FUNCTION_TRUNCATE_POINT_COORDS, false);
		pointMenu.add(_truncatePointCoordsItem);
		pointMenu.addSeparator();
		// find a waypoint
		_findWaypointItem = makeMenuItem(FunctionLibrary.FUNCTION_FIND_WAYPOINT, false);
		pointMenu.add(_findWaypointItem);
		_gotoPointMenu = new JMenu(I18nManager.getText("menu.point.goto"));
		_highestPointItem = makeMenuItem(new SelectExtremePoint(_app, SelectExtremePoint.Extreme.HIGHEST), false);
		_gotoPointMenu.add(_highestPointItem);
		_lowestPointItem = makeMenuItem(new SelectExtremePoint(_app, SelectExtremePoint.Extreme.LOWEST), false);
		_gotoPointMenu.add(_lowestPointItem);
		_fastestPointItem = makeMenuItem(new SelectExtremePoint(_app, SelectExtremePoint.Extreme.FASTEST), false);
		_gotoPointMenu.add(_fastestPointItem);
		pointMenu.add(_gotoPointMenu);
		// duplicate current point
		_duplicatePointItem = makeMenuItem(FunctionLibrary.FUNCTION_DUPLICATE_POINT, false);
		pointMenu.add(_duplicatePointItem);
		// project current point
		_projectPointItem = makeMenuItem(FunctionLibrary.FUNCTION_PROJECT_POINT, false);
		pointMenu.add(_projectPointItem);
		_projectCircleItem = makeMenuItem(FunctionLibrary.FUNCTION_PROJECT_CIRCLE, false);
		pointMenu.add(_projectCircleItem);
		pointMenu.addSeparator();
		// paste coordinates function
		JMenuItem pasteCoordsItem = makeMenuItem(new PasteCoordinates(_app));
		pointMenu.add(pasteCoordsItem);
		JMenuItem pasteCoordsListItem = makeMenuItem(new PasteCoordinateList(_app));
		pointMenu.add(pasteCoordsListItem);
		// pluscodes function
		JMenuItem plusCodeItem = makeMenuItem(new PlusCodeFunction(_app));
		pointMenu.add(plusCodeItem);
		menubar.add(pointMenu);

		// Add view menu
		JMenu viewMenu = new JMenu(I18nManager.getText("menu.view"));
		setAltKey(viewMenu, "altkey.menu.view");
		// Turn map display on/off
		_mapCheckbox = new JCheckBoxMenuItem(
			I18nManager.getText("menu.map.showmap"), false);
		_mapCheckbox.addActionListener(e -> {
			getConfig().setConfigBoolean(Config.KEY_SHOW_MAP, _mapCheckbox.isSelected());
			UpdateMessageBroker.informSubscribers(MAPSERVER_CHANGED);
		});
		viewMenu.add(_mapCheckbox);
		// Turn off the sidebars
		JCheckBoxMenuItem sidebarsCheckbox = new JCheckBoxMenuItem(I18nManager.getText("menu.view.showsidebars"));
		sidebarsCheckbox.setSelected(true);
		sidebarsCheckbox.addActionListener(e -> _app.toggleSidebars());
		sidebarsCheckbox.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F11, 0)); // shortcut F11
		viewMenu.add(sidebarsCheckbox);
		// 3d
		_show3dItem = makeMenuItem(FunctionLibrary.FUNCTION_3D_VIEW, false);
		viewMenu.add(_show3dItem);
		// Charts
		_chartItem = makeMenuItem(FunctionLibrary.FUNCTION_CHARTS, false);
		viewMenu.add(_chartItem);
		viewMenu.addSeparator();
		// Distances
		_distanceItem = makeMenuItem(FunctionLibrary.FUNCTION_DISTANCES, false);
		viewMenu.add(_distanceItem);
		// full range details
		_viewFullDetailsItem = makeMenuItem(FunctionLibrary.FUNCTION_FULL_DETAILS, false);
		setShortcut(_viewFullDetailsItem, "shortcut.menu.view.details");
		viewMenu.add(_viewFullDetailsItem);
		// estimate time
		_estimateTimeItem = makeMenuItem(FunctionLibrary.FUNCTION_ESTIMATE_TIME, false);
		viewMenu.add(_estimateTimeItem);
		// Compare segments
		_compareSegmentsItem = makeMenuItem(new CompareSegmentsFunction(_app), false);
		viewMenu.add(_compareSegmentsItem);
		viewMenu.addSeparator();
		// autoplay
		_autoplayTrack = makeMenuItem(FunctionLibrary.FUNCTION_AUTOPLAY_TRACK, false);
		viewMenu.add(_autoplayTrack);
		menubar.add(viewMenu);

		// Add photo menu
		JMenu photoMenu = new JMenu(I18nManager.getText("menu.photo"));
		setAltKey(photoMenu, "altkey.menu.photo");
		addPhotosMenuItem = new JMenuItem(I18nManager.getText("menu.file.addphotos"));
		addPhotosMenuItem.addActionListener(e -> _app.addPhotos());
		photoMenu.add(addPhotosMenuItem);
		_saveExifItem = new JMenuItem(I18nManager.getText("menu.photo.saveexif"));
		_saveExifItem.addActionListener(e -> _app.saveExif());
		_saveExifItem.setEnabled(false);
		photoMenu.add(_saveExifItem);
		// Deselect current photo
		_selectNoPhotoItem = new JMenuItem(I18nManager.getText("menu.range.none"));
		_selectNoPhotoItem.setEnabled(false);
		_selectNoPhotoItem.addActionListener(e -> _app.getTrackInfo().selectPhoto(-1));
		photoMenu.add(_selectNoPhotoItem);
		photoMenu.addSeparator();
		_connectPhotoItem = makeMenuItem(FunctionLibrary.FUNCTION_CONNECT_TO_POINT, false);
		photoMenu.add(_connectPhotoItem);
		// disconnect photo
		_disconnectPhotoItem = makeMenuItem(FunctionLibrary.FUNCTION_DISCONNECT_PHOTO, false);
		photoMenu.add(_disconnectPhotoItem);
		_removePhotoItem = makeMenuItem(FunctionLibrary.FUNCTION_REMOVE_PHOTO, false);
		photoMenu.add(_removePhotoItem);
		// Rotate current photo
		_rotatePhotoLeft = makeMenuItem(FunctionLibrary.FUNCTION_ROTATE_PHOTO_LEFT, false);
		photoMenu.add(_rotatePhotoLeft);
		_rotatePhotoRight = makeMenuItem(FunctionLibrary.FUNCTION_ROTATE_PHOTO_RIGHT, false);
		photoMenu.add(_rotatePhotoRight);
		// Show photo popup
		_photoPopupItem = makeMenuItem(FunctionLibrary.FUNCTION_PHOTO_POPUP, false);
		photoMenu.add(_photoPopupItem);
		_ignoreExifThumb = makeMenuItem(FunctionLibrary.FUNCTION_IGNORE_EXIF_THUMB, false);
		photoMenu.add(_ignoreExifThumb);
		photoMenu.addSeparator();
		// correlate all photos
		_correlatePhotosItem = makeMenuItem(FunctionLibrary.FUNCTION_CORRELATE_PHOTOS, false);
		photoMenu.add(_correlatePhotosItem);
		// rearrange photo points
		_rearrangePhotosItem = makeMenuItem(FunctionLibrary.FUNCTION_REARRANGE_PHOTOS, false);
		photoMenu.add(_rearrangePhotosItem);
		menubar.add(photoMenu);

		// Audio menu
		JMenu audioMenu = new JMenu(I18nManager.getText("menu.audio"));
		setAltKey(audioMenu, "altkey.menu.audio");
		addAudioMenuItem = makeMenuItem(FunctionLibrary.FUNCTION_LOAD_AUDIO);
		audioMenu.add(addAudioMenuItem);
		_selectNoAudioItem = new JMenuItem(I18nManager.getText("menu.range.none"));
		_selectNoAudioItem.setEnabled(false);
		_selectNoAudioItem.addActionListener(e -> _app.getTrackInfo().selectAudio(-1));
		audioMenu.add(_selectNoAudioItem);
		audioMenu.addSeparator();
		// connect audio
		_connectAudioItem = makeMenuItem(FunctionLibrary.FUNCTION_CONNECT_TO_POINT, false);
		audioMenu.add(_connectAudioItem);
		// Disconnect current audio clip
		_disconnectAudioItem = makeMenuItem(FunctionLibrary.FUNCTION_DISCONNECT_AUDIO, false);
		audioMenu.add(_disconnectAudioItem);
		// Remove current audio clip
		_removeAudioItem = makeMenuItem(FunctionLibrary.FUNCTION_REMOVE_AUDIO, false);
		audioMenu.add(_removeAudioItem);
		audioMenu.addSeparator();
		// Correlate audio clips
		_correlateAudiosItem = makeMenuItem(FunctionLibrary.FUNCTION_CORRELATE_AUDIOS, false);
		audioMenu.add(_correlateAudiosItem);
		menubar.add(audioMenu);

		// Settings menu
		JMenu settingsMenu = new JMenu(I18nManager.getText("menu.settings"));
		setAltKey(settingsMenu, "altkey.menu.settings");
		// Set the map background
		JMenuItem mapBgItem = makeMenuItem(FunctionLibrary.FUNCTION_SET_MAP_BG);
		settingsMenu.add(mapBgItem);
		_onlineCheckbox = new JCheckBoxMenuItem(I18nManager.getText("menu.settings.onlinemode"));
		_onlineCheckbox.setSelected(getConfig().getConfigBoolean(Config.KEY_ONLINE_MODE));
		_onlineCheckbox.addActionListener(e -> {
			boolean isOnline = _onlineCheckbox.isSelected();
			getConfig().setConfigBoolean(Config.KEY_ONLINE_MODE, isOnline);
			if (isOnline) {UpdateMessageBroker.informSubscribers();}
		});
		settingsMenu.add(_onlineCheckbox);
		settingsMenu.add(makeMenuItem(FunctionLibrary.FUNCTION_SET_DISK_CACHE));
		settingsMenu.addSeparator();
		// Set program paths
		settingsMenu.add(makeMenuItem(FunctionLibrary.FUNCTION_SET_PATHS));
		// Set colours
		settingsMenu.add(makeMenuItem(FunctionLibrary.FUNCTION_SET_COLOURS));
		// display settings
		settingsMenu.add(makeMenuItem(FunctionLibrary.FUNCTION_SET_DISPLAY_SETTINGS));
		settingsMenu.add(makeMenuItem(FunctionLibrary.FUNCTION_SET_WAYPOINT_DISPLAY));
		// Set language
		settingsMenu.add(makeMenuItem(FunctionLibrary.FUNCTION_SET_LANGUAGE));
		// Set altitude tolerance
		settingsMenu.add(makeMenuItem(new ChooseSingleParameter(_app, FunctionLibrary.FUNCTION_SET_ALTITUDE_TOLERANCE)));
		// Set timezone
		settingsMenu.add(makeMenuItem(FunctionLibrary.FUNCTION_SET_TIMEZONE));
		settingsMenu.addSeparator();
		// Save configuration
		settingsMenu.add(makeMenuItem(FunctionLibrary.FUNCTION_SAVECONFIG));
		_autosaveSettingsCheckbox = new JCheckBoxMenuItem(
			I18nManager.getText("menu.settings.autosave"), false);
		_autosaveSettingsCheckbox.setSelected(getConfig().getConfigBoolean(Config.KEY_AUTOSAVE_SETTINGS));
		_autosaveSettingsCheckbox.addActionListener(e -> {
			final boolean autosaveOn = _autosaveSettingsCheckbox.isSelected();
			getConfig().setConfigBoolean(Config.KEY_AUTOSAVE_SETTINGS, autosaveOn);
			// Maybe want to save config?
			new SaveConfig(_app).autosaveSwitched(autosaveOn);
		});
		settingsMenu.add(_autosaveSettingsCheckbox);
		menubar.add(settingsMenu);

		// Help menu
		JMenu helpMenu = new JMenu(I18nManager.getText("menu.help"));
		setAltKey(helpMenu, "altkey.menu.help");
		JMenuItem helpItem = makeMenuItem(FunctionLibrary.FUNCTION_HELP);
		setShortcut(helpItem, "shortcut.menu.help.help");
		helpMenu.add(helpItem);
		helpMenu.add(makeMenuItem(FunctionLibrary.FUNCTION_SHOW_KEYS));
		helpMenu.add(makeMenuItem(FunctionLibrary.FUNCTION_ABOUT));
		helpMenu.add(makeMenuItem(FunctionLibrary.FUNCTION_CHECK_VERSION));
		menubar.add(helpMenu);

		return menubar;
	}

	/**
	 * Convenience method for making a menu item using a function
	 * @param inFunction function
	 * @param inEnabled flag to specify initial enabled state
	 * @return menu item using localized name of function
	 */
	private static JMenuItem makeMenuItem(GenericFunction inFunction, boolean inEnabled)
	{
		JMenuItem item = makeMenuItem(inFunction);
		item.setEnabled(inEnabled);
		return item;
	}

	/**
	 * Convenience method for making a menu item using a function
	 * @param inFunction function
	 * @return menu item using localized name of function
	 */
	private static JMenuItem makeMenuItem(GenericFunction inFunction)
	{
		JMenuItem item = new JMenuItem(inFunction.getName());
		item.addActionListener(new FunctionLauncher(inFunction));
		return item;
	}

	/**
	 * Set the alt key for the given menu
	 * @param inMenu menu to set
	 * @param inKey key to lookup to get language-sensitive altkey
	 */
	private static void setAltKey(JMenu inMenu, String inKey)
	{
		// Lookup the key in the properties
		String altKey = I18nManager.getText(inKey);
		if (altKey != null && altKey.length() == 1)
		{
			int code = altKey.charAt(0) - 'A';
			if (code >= 0 && code < 26)
			{
				// Found a valid code between A and Z
				inMenu.setMnemonic(KEY_EVENTS[code]);
			}
		}
	}

	/**
	 * Set the shortcut key for the given menu item
	 * @param inMenuItem menu item to set
	 * @param inKey key to lookup to get language-sensitive shortcut
	 */
	private static void setShortcut(JMenuItem inMenuItem, String inKey)
	{
		// Lookup the key in the properties
		String altKey = I18nManager.getText(inKey);
		if (altKey != null && altKey.length() == 1)
		{
			int code = altKey.charAt(0) - 'A';
			if (code >= 0 && code < 26)
			{
				// Found a valid code between A and Z
				inMenuItem.setAccelerator(KeyStroke.getKeyStroke(KEY_EVENTS[code],
					Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx()));
				// use platform-specific key mask so Ctrl on Linux/Win, Clover on Mac
			}
		}
	}

	/**
	 * @return toolbar containing buttons
	 */
	public JToolBar createToolBar()
	{
		JToolBar toolbar = new JToolBar();
		// Open / import file
		JButton openFileButton = makeToolbarButton(IconManager.TOOLBAR_IMPORT_FILE, "function.open", e -> _app.openFile());
		openFileButton.setEnabled(true);
		toolbar.add(openFileButton);
		// Add photo
		JButton addPhotoButton = makeToolbarButton(IconManager.TOOLBAR_ADD_PHOTO, "menu.file.addphotos", e -> _app.addPhotos());
		addPhotoButton.setEnabled(true);
		toolbar.add(addPhotoButton);
		// Save / export file
		_saveButton = makeToolbarButton(IconManager.TOOLBAR_EXPORT_FILE, "menu.file.export", e -> showExportMenu());
		toolbar.add(_saveButton);
		// Undo, Redo
		_undoButton = makeToolbarButton(IconManager.TOOLBAR_UNDO, "menu.track.undo", e -> _app.beginUndo());
		toolbar.add(_undoButton);
		_redoButton = makeToolbarButton(IconManager.TOOLBAR_REDO, "menu.track.redo", e -> _app.beginRedo());
		toolbar.add(_redoButton);
		// Edit point
		_editPointButton = makeToolbarButton(IconManager.TOOLBAR_EDIT_POINT, new PointEditor(_app));
		toolbar.add(_editPointButton);
		// Delete point
		_deletePointButton = makeToolbarButton(IconManager.TOOLBAR_DELETE_POINT, new DeleteCurrentPoint(_app));
		toolbar.add(_deletePointButton);
		// Delete range
		_deleteRangeButton = makeToolbarButton(IconManager.TOOLBAR_DELETE_RANGE, FunctionLibrary.FUNCTION_DELETE_RANGE);
		toolbar.add(_deleteRangeButton);
		// Cut and move
		_cutAndMoveButton = makeToolbarButton(IconManager.TOOLBAR_CUT_AND_MOVE, new CutAndMoveFunction(_app));
		toolbar.add(_cutAndMoveButton);
		// Select start, end
		_selectStartButton = makeToolbarButton(IconManager.TOOLBAR_SET_RANGE_START, "menu.range.start", e -> _selection.selectRangeStart());
		toolbar.add(_selectStartButton);
		_selectEndButton = makeToolbarButton(IconManager.TOOLBAR_SET_RANGE_END, "menu.range.end", e -> _selection.selectRangeEnd());
		toolbar.add(_selectEndButton);
		// View full details
		_viewInfoButton = makeToolbarButton(IconManager.TOOLBAR_VIEW_INFO, FunctionLibrary.FUNCTION_FULL_DETAILS);
		toolbar.add(_viewInfoButton);
		// Connect to point
		_connectButton = makeToolbarButton(IconManager.TOOLBAR_CONNECT_PHOTO, FunctionLibrary.FUNCTION_CONNECT_TO_POINT);
		toolbar.add(_connectButton);
		// 3d
		_threeDeeButton = makeToolbarButton(IconManager.TOOLBAR_VIEW_3D, FunctionLibrary.FUNCTION_3D_VIEW);
		toolbar.add(_threeDeeButton);
		// finish off
		toolbar.setFloatable(false);
		return toolbar;
	}

	/**
	 * Make a button object for the toolbar
	 * @param inIconName name of the icon filename
	 * @param inFunction function to call
	 */
	private JButton makeToolbarButton(String inIconName, GenericFunction inFunction)
	{
		return makeToolbarButton(inIconName, inFunction.getNameKey(), e -> inFunction.begin());
	}

	/**
	 * Make a button object for the toolbar
	 * @param inIconName name of the icon filename
	 * @param inTooltipKey key for the tooltip text
	 * @param inListener listener which responds to action
	 */
	private JButton makeToolbarButton(String inIconName, String inTooltipKey, ActionListener inListener)
	{
		JButton button = new JButton(_app.getIconManager().getImageIcon(inIconName));
		button.setEnabled(false);
		button.setBorder(BorderFactory.createEmptyBorder(3, 8, 3, 8));
		button.setBorder(BorderFactory.createEmptyBorder(3, 8, 3, 8));
		button.setToolTipText(I18nManager.getText(inTooltipKey));
		button.addActionListener(inListener);
		return button;
	}


	/**
	 * @see tim.prune.DataSubscriber#dataUpdated
	 */
	public void dataUpdated(int inUpdateType)
	{
		final boolean hasData = _track != null && _track.getNumPoints() > 0;
		final boolean hasMultiplePoints = hasData && _track.getNumPoints() > 1;

		// set functions which require data
		_sendGpsItem.setEnabled(hasData);
		_saveItem.setEnabled(hasData);
		_saveButton.setEnabled(hasData);
		_exportGpxItem.setEnabled(hasData);
		_exportKmlItem.setEnabled(hasData);
		_exportPovItem.setEnabled(hasMultiplePoints);
		_exportImageItem.setEnabled(hasMultiplePoints);
		_compressItem.setEnabled(hasData);
		_markRectangleInsideItem.setEnabled(hasData);
		_markRectangleOutsideItem.setEnabled(hasData);
		_deleteMarkedPointsItem.setEnabled(hasData && _app.getTrackInfo().hasPointsMarkedForDeletion());
		_rearrangeWaypointsItem.setEnabled(hasData && _track.hasWaypoints() && _track.getNumPoints() > 1);
		_dedupeWaypointsItem.setEnabled(hasData && _track.hasWaypoints() && _track.getNumPoints() > 1);
		_viewFullDetailsItem.setEnabled(hasData);
		_viewInfoButton.setEnabled(hasData);
		final boolean hasSeveralTrackPoints = hasData && _track.hasTrackPoints() && _track.getNumPoints() > 3;
		_splitSegmentsItem.setEnabled(hasSeveralTrackPoints);
		_sewSegmentsItem.setEnabled(hasSeveralTrackPoints);
		_createMarkerWaypointsItem.setEnabled(hasSeveralTrackPoints);
		_compareSegmentsItem.setEnabled(hasSeveralTrackPoints);
		_selectAllItem.setEnabled(hasData);
		_selectNoneItem.setEnabled(hasData);
		_show3dItem.setEnabled(hasMultiplePoints && _threeDAvailable);
		_threeDeeButton.setEnabled(hasMultiplePoints && _threeDAvailable);
		_chartItem.setEnabled(hasData);
		_browserMapMenu.setEnabled(hasData);
		_distanceItem.setEnabled(hasData);
		_autoplayTrack.setEnabled(hasData && _track.getNumPoints() > 3);
		_lookupSrtmItem.setEnabled(hasData);
		_nearbyWikipediaItem.setEnabled(hasData);
		_nearbyOsmPoiItem.setEnabled(hasData);
		_downloadOsmItem.setEnabled(hasData);
		_getWeatherItem.setEnabled(hasData);
		_findWaypointItem.setEnabled(hasData && _track.hasWaypoints());
		_gotoPointMenu.setEnabled(hasData);
		_highestPointItem.setEnabled(hasMultiplePoints && _track.hasAltitudeData());
		_lowestPointItem.setEnabled(hasMultiplePoints && _track.hasAltitudeData());
		// have we got any timestamps?
		_deleteByDateItem.setEnabled(hasData && _track.hasData(Field.TIMESTAMP));
		_fastestPointItem.setEnabled(hasMultiplePoints && (_track.hasData(Field.TIMESTAMP) || _track.hasData(Field.SPEED)));

		// is undo available?
		boolean hasUndo = !_app.getUndoStack().isEmpty();
		_undoItem.setEnabled(hasUndo);
		_undoButton.setEnabled(hasUndo);
		_clearUndoItem.setEnabled(hasUndo);
		boolean hasRedo = !_app.getRedoStack().isEmpty();
		_redoButton.setEnabled(hasRedo);
		_redoItem.setEnabled(hasRedo);
		// is there a current point?
		DataPoint currPoint = _app.getTrackInfo().getCurrentPoint();
		boolean hasPoint = (currPoint != null);
		_editPointItem.setEnabled(hasPoint);
		_editPointButton.setEnabled(hasPoint);
		_editWaypointNameItem.setEnabled(hasPoint);
		_togglePointSegmentItem.setEnabled(hasPoint && !currPoint.isWaypoint()
			&& _app.getTrackInfo().getSelection().getCurrentPointIndex() > 0);
		_deletePointItem.setEnabled(hasPoint);
		_deletePointButton.setEnabled(hasPoint);
		_truncatePointCoordsItem.setEnabled(hasPoint);
		_selectStartItem.setEnabled(hasPoint);
		_selectStartButton.setEnabled(hasPoint);
		_selectEndItem.setEnabled(hasPoint);
		_selectEndButton.setEnabled(hasPoint);
		_duplicatePointItem.setEnabled(hasPoint);
		_projectPointItem.setEnabled(hasPoint);
		_projectCircleItem.setEnabled(hasPoint);
		_showPeakfinderItem.setEnabled(hasPoint);
		_showGeohackItem.setEnabled(hasPoint);
		_searchOpencachingDeItem.setEnabled(hasPoint);
		_openCachePageItem.setEnabled(hasPoint && GeocacheCode.isValidCode(currPoint.getWaypointName()));
		// is it a waypoint?
		_selectSegmentItem.setEnabled(hasPoint && !currPoint.isWaypoint());
		// are there any photos?
		boolean anyPhotos = _app.getTrackInfo().getPhotoList().hasAny();
		_saveExifItem.setEnabled(anyPhotos && _app.getTrackInfo().getPhotoList().hasMediaWithFile());
		// is there a current photo, audio?
		Photo currentPhoto = _app.getTrackInfo().getCurrentPhoto();
		boolean hasPhoto = currentPhoto != null;
		AudioClip currentAudio = _app.getTrackInfo().getCurrentAudio();
		boolean hasAudio = currentAudio != null;
		// connect is available if (photo/audio) and point selected, and media has no point
		boolean connectAvailable = (hasPhoto && hasPoint && currentPhoto.getDataPoint() == null)
			|| (hasAudio && hasPoint && currentAudio.getDataPoint() == null);
		_connectPhotoItem.setEnabled(hasPhoto && hasPoint && currentPhoto.getDataPoint() == null);
		_connectButton.setEnabled(connectAvailable);
		_disconnectPhotoItem.setEnabled(hasPhoto && currentPhoto.getDataPoint() != null);
		_correlatePhotosItem.setEnabled(anyPhotos && hasData);
		_rearrangePhotosItem.setEnabled(anyPhotos && hasMultiplePoints);
		_removePhotoItem.setEnabled(hasPhoto);
		_rotatePhotoLeft.setEnabled(hasPhoto);
		_rotatePhotoRight.setEnabled(hasPhoto);
		_photoPopupItem.setEnabled(hasPhoto);
		_ignoreExifThumb.setEnabled(hasPhoto && currentPhoto.getExifThumbnail() != null);
		_selectNoPhotoItem.setEnabled(hasPhoto);
		boolean anyAudios = _app.getTrackInfo().getAudioList().hasAny();
		_selectNoAudioItem.setEnabled(hasAudio);
		_removeAudioItem.setEnabled(hasAudio);
		_connectAudioItem.setEnabled(hasAudio && hasPoint && currentAudio.getDataPoint() == null);
		_disconnectAudioItem.setEnabled(hasAudio && currentAudio.getDataPoint() != null);
		_correlateAudiosItem.setEnabled(anyAudios && hasData);
		// is there a current range?
		boolean hasRange = (hasData && _selection.hasRangeSelected());
		_deleteRangeItem.setEnabled(hasRange);
		_deleteRangeButton.setEnabled(hasRange);
		_cropTrackItem.setEnabled(hasRange);
		boolean rangeHasTimestamps = hasData && _track.hasData(Field.TIMESTAMP, _selection.getStart(), _selection.getEnd());
		_interpolateTimestampsItem.setEnabled(hasRange && rangeHasTimestamps && _track.hasTrackPoints());
		_interpolateItem.setEnabled(hasRange);
		_averageItem.setEnabled(hasRange);
		_mergeSegmentsItem.setEnabled(hasRange);
		_reverseItem.setEnabled(hasRange);
		_addTimeOffsetItem.setEnabled(hasRange);
		_addAltitudeOffsetItem.setEnabled(hasRange);
		_projectRangeItem.setEnabled(hasRange);
		_deleteFieldValuesItem.setEnabled(hasRange);
		_estimateTimeItem.setEnabled(hasRange && _track.hasTrackPoints());
		_learnEstimationParams.setEnabled(hasData && _track.hasTrackPoints() && _track.hasData(Field.TIMESTAMP)
			&& _track.hasAltitudeData());
		// Is the currently selected point outside the current range?
		boolean canCutAndMove = hasRange && hasPoint &&
			(_selection.getCurrentPointIndex() < _selection.getStart()
			|| _selection.getCurrentPointIndex() > (_selection.getEnd()+1));
		_cutAndMoveItem.setEnabled(canCutAndMove);
		_cutAndMoveButton.setEnabled(canCutAndMove);
		final boolean isTrackLengthTwo = hasData && _track.getNumPoints() == 2;
		_routingGraphHopperItem.setEnabled(isTrackLengthTwo || (hasData && hasRange));
		// Has the map been switched on/off?
		boolean mapsOn = getConfig().getConfigBoolean(Config.KEY_SHOW_MAP);
		if (_mapCheckbox.isSelected() != mapsOn) {
			_mapCheckbox.setSelected(mapsOn);
		}
		// Are there any recently-used files?
		RecentFileList rfl = getConfig().getRecentFileList();
		final int numRecentFiles = rfl.getNumEntries();
		final boolean hasRecentFiles = numRecentFiles > 0;
		_recentFileMenu.setEnabled(hasRecentFiles);
		if (hasRecentFiles)
		{
			int numItems = _recentFileMenu.getMenuComponentCount();
			if (numItems == numRecentFiles)
			{
				// Right number of items, just change texts
				for (int i=0; i<numRecentFiles; i++)
				{
					JMenuItem item = _recentFileMenu.getItem(i);
					RecentFile rf = rfl.getFile(i);
					item.setText(rf==null?"":rf.getFile().getName());
					item.setToolTipText(rf==null?null:rf.getFile().getAbsolutePath());
				}
			}
			else
			{
				rfl.verifyAll();
				// Rebuild menus
				_recentFileMenu.removeAll();
				for (int i=0; i<rfl.getCapacity(); i++)
				{
					RecentFile rf = rfl.getFile(i);
					if (rf != null && rf.isValid())
					{
						JMenuItem menuItem = new JMenuItem(rf.getFile().getName());
						menuItem.setToolTipText(rf.getFile().getAbsolutePath());
						menuItem.addActionListener(new RecentFileTrigger(_app, i));
						_recentFileMenu.add(menuItem);
					}
				}
			}
		}
	}

	/**
	 * React to export button on toolbar
	 */
	private void showExportMenu()
	{
		JPopupMenu popup = new JPopupMenu();
		JMenuItem saveTextItem = new JMenuItem(I18nManager.getText("menu.file.save"));
		saveTextItem.addActionListener(e -> _app.saveFile());
		popup.add(saveTextItem);
		JMenuItem exportGpxItem = new JMenuItem(I18nManager.getText("function.exportgpx"));
		exportGpxItem.addActionListener(e -> FunctionLibrary.FUNCTION_GPXEXPORT.begin());
		popup.add(exportGpxItem);
		JMenuItem exportKmlItem = new JMenuItem(I18nManager.getText("function.exportkml"));
		exportKmlItem.addActionListener(e -> FunctionLibrary.FUNCTION_KMLEXPORT.begin());
		popup.add(exportKmlItem);
		JMenuItem exportImageItem = new JMenuItem(I18nManager.getText("function.exportimage"));
		exportImageItem.addActionListener(e -> FunctionLibrary.FUNCTION_IMAGEEXPORT.begin());
		popup.add(exportImageItem);
		popup.show(_saveButton, 20, 15);
	}

	/**
	 * Ignore action completed signals
	 * @see tim.prune.DataSubscriber#actionCompleted(java.lang.String)
	 */
	public void actionCompleted(String inMessage)
	{}
}
