content: package tim.prune.gui;
  
  import java.awt.BorderLayout;
  import java.awt.Component;
  import java.awt.Dimension;
  import java.awt.Font;
  import java.awt.GridLayout;
  
  import javax.swing.BorderFactory;
  import javax.swing.Box;
  import javax.swing.BoxLayout;
  import javax.swing.JLabel;
  import javax.swing.JList;
  import javax.swing.JPanel;
  import javax.swing.JScrollBar;
  import javax.swing.JScrollPane;
  import javax.swing.ListSelectionModel;
  import javax.swing.border.EtchedBorder;
  
  import tim.prune.DataSubscriber;
  import tim.prune.I18nManager;
  import tim.prune.data.TrackInfo;
  
  /**
   * Class to allow selection of points and photos
   * as a visual component
   */
  public class SelectorDisplay extends GenericDisplay
  {
  	// Track details
  	private JLabel _trackpointsLabel = null;
  	private JLabel _filenameLabel = null;
  	// Scroll bar
  	private JScrollBar _scroller = null;
  	private boolean _ignoreScrollEvents = false;
  
  	// Panel containing lists
  	private JPanel _listsPanel = null;
  	private int _visiblePanels = 1;
  	// Waypoints
  	private JPanel _waypointListPanel = null;
  	private JList<String> _waypointList = null;
  	private WaypointListModel _waypointListModel = null;
  	// Photos
  	private JPanel _photoListPanel = null;
  	private JList<String> _photoList = null;
  	private MediaListModel _photoListModel = null;
  	// Audio files
  	private JPanel _audioListPanel = null;
  	private JList<String> _audioList = null;
  	private MediaListModel _audioListModel = null;
  
  	// scrollbar interval
  	private static final int SCROLLBAR_INTERVAL = 50;
  	// number of rows in lists
  	private static final int NUM_LIST_ENTRIES = 7;
  
  
  	/**
  	 * Constructor
  	 * @param inTrackInfo Track info object
  	 */
  	public SelectorDisplay(TrackInfo inTrackInfo)
  	{
  		super(inTrackInfo);
  		setLayout(new BorderLayout());
  
  		JPanel mainPanel = new JPanel();
  		mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
  		mainPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
  
  		// Track details panel
  		JPanel trackDetailsPanel = new JPanel();
  		trackDetailsPanel.setLayout(new BoxLayout(trackDetailsPanel, BoxLayout.Y_AXIS));
  		trackDetailsPanel.setBorder(BorderFactory.createCompoundBorder(
  			BorderFactory.createEtchedBorder(EtchedBorder.LOWERED), BorderFactory.createEmptyBorder(3, 3, 3, 3))
  		);
  		JLabel trackDetailsLabel = new JLabel(I18nManager.getText("details.trackdetails"));
  		Font biggerFont = trackDetailsLabel.getFont();
  		biggerFont = biggerFont.deriveFont(Font.BOLD, biggerFont.getSize2D() + 2.0f);
  		trackDetailsLabel.setFont(biggerFont);
  		trackDetailsPanel.add(trackDetailsLabel);
  		_trackpointsLabel = new JLabel(I18nManager.getText("details.notrack"));
  		trackDetailsPanel.add(_trackpointsLabel);
  		_filenameLabel = new JLabel("");
  		_filenameLabel.setMinimumSize(new Dimension(120, 10));
  		trackDetailsPanel.add(_filenameLabel);
  
  		// Scroll bar
  		_scroller = new JScrollBar(JScrollBar.HORIZONTAL, 0, SCROLLBAR_INTERVAL, 0, 100);
  		_scroller.addAdjustmentListener(e -> selectPoint(e.getValue()));
  		_scroller.setEnabled(false);
  
  		// Add panel for waypoints / photos
  		_listsPanel = new JPanel();
  		_listsPanel.setLayout(new GridLayout(0, 1));
  		_listsPanel.setBorder(BorderFactory.createCompoundBorder(
  			BorderFactory.createEtchedBorder(EtchedBorder.LOWERED), BorderFactory.createEmptyBorder(3, 3, 3, 3))
  		);
  		_waypointListModel = new WaypointListModel(_trackInfo.getTrack());
  		_waypointList = new JList<String>(_waypointListModel);
  		_waypointList.setVisibleRowCount(NUM_LIST_ENTRIES);
  		_waypointList.addListSelectionListener(e -> {
  			if (!e.getValueIsAdjusting()) {
  				selectWaypoint(_waypointList.getSelectedIndex());
  			}
  		});
  		_waypointListPanel = makeListPanel("details.lists.waypoints", _waypointList);
  		_listsPanel.add(_waypointListPanel);
  		// photo list
  		_photoListModel = new MediaListModel(_trackInfo.getPhotoList());
  		_photoList = new JList<String>(_photoListModel);
  		_photoList.setVisibleRowCount(NUM_LIST_ENTRIES);
  		_photoList.addListSelectionListener(e -> {
  			if (!e.getValueIsAdjusting()) {
  				selectPhoto(_photoList.getSelectedIndex());
  			}
  		});
  		_photoListPanel = makeListPanel("details.lists.photos", _photoList);
  		// don't add photo list (because there aren't any photos yet)
  
  		// List for audio clips
  		_audioListModel = new MediaListModel(_trackInfo.getAudioList());
  		_audioList = new JList<String>(_audioListModel);
  		_audioList.addListSelectionListener(e -> {
  			if (!e.getValueIsAdjusting()) {
  				selectAudio(_audioList.getSelectedIndex());
  			}
  		});
  		_audioListPanel = makeListPanel("details.lists.audio", _audioList);
  		// don't add audio list either
  		_listsPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
  
  		// add the controls to the main panel
  		mainPanel.add(trackDetailsPanel);
  		mainPanel.add(Box.createVerticalStrut(5));
  		mainPanel.add(_scroller);
  		mainPanel.add(Box.createVerticalStrut(5));
  
  		// add the main panel at the top
  		add(mainPanel, BorderLayout.NORTH);
  		// and lists in the centre
  		add(_listsPanel, BorderLayout.CENTER);
  		// set preferred width to be small
  		setPreferredSize(new Dimension(100, 100));
  	}
  
  
  	/**
  	 * Select the specified point
  	 * @param inValue value to select
  	 */
  	private void selectPoint(int inValue)
  	{
  		if (_track != null && !_ignoreScrollEvents) {
  			_trackInfo.selectPoint(inValue);
  		}
  	}
  
  
  	/**
  	 * Select the specified photo
  	 * @param inPhotoIndex index of selected photo
  	 */
  	private void selectPhoto(int inPhotoIndex)
  	{
  		_trackInfo.selectPhoto(inPhotoIndex);
  	}
  
  	/**
  	 * Select the specified audio clip
  	 * @param inIndex index of selected audio clip
  	 */
  	private void selectAudio(int inIndex)
  	{
  		_trackInfo.selectAudio(inIndex);
  	}
  
  	/**
  	 * Select the specified waypoint
  	 * @param inWaypointIndex index of selected waypoint
  	 */
  	private void selectWaypoint(int inWaypointIndex)
  	{
  		if (inWaypointIndex >= 0) {
  			_trackInfo.selectPoint(_waypointListModel.getWaypoint(inWaypointIndex));
  		}
  	}
  
  
  	/**
  	 * Notification that Track has been updated
  	 */
  	public void dataUpdated(int inUpdateType)
  	{
  		// Update track data
  		if (_track == null || _track.getNumPoints() <= 0)
  		{
  			_trackpointsLabel.setText(I18nManager.getText("details.notrack"));
  			_filenameLabel.setText("");
  			_filenameLabel.setToolTipText("");
  		}
  		else
  		{
  			_trackpointsLabel.setText(I18nManager.getText("details.track.points") + ": "
  				+ _track.getNumPoints());
  			int numFiles = _trackInfo.getFileInfo().getNumFiles();
  			if (numFiles == 1)
  			{
  				final String filenameString = _trackInfo.getFileInfo().getFilename();
  				_filenameLabel.setText(I18nManager.getText("details.track.file") + ": "
  					+ filenameString);
  				_filenameLabel.setToolTipText(filenameString);
  			}
  			else if (numFiles > 1)
  			{
  				final String labelText = I18nManager.getText("details.track.numfiles") + ": " + numFiles;
  				_filenameLabel.setText(labelText);
  				_filenameLabel.setToolTipText(labelText);
  			}
  			else
  			{
  				_filenameLabel.setText("");
  				_filenameLabel.setToolTipText("");
  			}
  		}
  
  		// Update scroller settings
  		int currentPointIndex = _trackInfo.getSelection().getCurrentPointIndex();
  		_ignoreScrollEvents = true;
  		if (_track == null || _track.getNumPoints() < 2)
  		{
  			// careful to avoid event loops here
  			// _scroller.setValue(0);
  			_scroller.setEnabled(false);
  		}
  		else
  		{
  			_scroller.setMaximum(_track.getNumPoints() -1 + SCROLLBAR_INTERVAL);
  			if (currentPointIndex >= 0)
  				_scroller.setValue(currentPointIndex);
  			_scroller.setEnabled(true);
  		}
  		_ignoreScrollEvents = false;
  
  		// update waypoints and photos if necessary
  		if ((inUpdateType &
  			(DataSubscriber.DATA_ADDED_OR_REMOVED | DataSubscriber.DATA_EDITED | DataSubscriber.WAYPOINTS_MODIFIED)) > 0)
  		{
  			_waypointListModel.fireChanged();
  		}
  		if ((inUpdateType &
  			(DataSubscriber.DATA_ADDED_OR_REMOVED | DataSubscriber.DATA_EDITED | DataSubscriber.MEDIA_MODIFIED)) > 0)
  		{
  			_photoListModel.fireChanged();
  			_audioListModel.fireChanged();
  		}
  		// Deselect selected waypoint if selected point has since changed
  		if (_waypointList.getSelectedIndex() >= 0)
  		{
  			if (_trackInfo.getCurrentPoint() == null
  			 || _waypointList.getSelectedIndex() >= _waypointListModel.getSize()
  			 || !_waypointListModel.getWaypoint(_waypointList.getSelectedIndex()).equals(_trackInfo.getCurrentPoint()))
  			{
  				// point is selected in list but different from current point - deselect
  				_waypointList.clearSelection();
  			}
  		}
  		// Hide photo list if no photos loaded, same for audio
  		redrawLists(_photoListModel.getSize() > 0, _audioListModel.getSize() > 0);
  
  		// Make sure correct photo is selected
  		if (_photoListModel.getSize() > 0)
  		{
  			int photoIndex = _trackInfo.getSelection().getCurrentPhotoIndex();
  			int listSelection = _photoList.getSelectedIndex();
  			// Change listbox selection if indexes not equal
  			if (listSelection != photoIndex)
  			{
  				if (photoIndex < 0) {
  					_photoList.clearSelection();
  				}
  				else {
  					_photoList.setSelectedIndex(photoIndex);
  				}
  			}
  		}
  		// Same for audio clips
  		if (_audioListModel.getSize() > 0)
  		{
  			int audioIndex = _trackInfo.getSelection().getCurrentAudioIndex();
  			int listSelection = _audioList.getSelectedIndex();
  			// Change listbox selection if indexes not equal
  			if (listSelection != audioIndex)
  			{
  				if (audioIndex < 0) {
  					_audioList.clearSelection();
  				}
  				else {
  					_audioList.setSelectedIndex(audioIndex);
  				}
  			}
  		}
  	}
  
  	/**
  	 * Make one of the three list panels
  	 * @param inNameKey key for heading text
  	 * @param inList list object
  	 * @return panel object
  	 */
  	private static JPanel makeListPanel(String inNameKey, JList<String> inList)
  	{
  		JPanel panel = new JPanel();
  		panel.setLayout(new BorderLayout());
  		panel.add(new JLabel(I18nManager.getText(inNameKey)), BorderLayout.NORTH);
  		inList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
  		panel.add(new JScrollPane(inList), BorderLayout.CENTER);
  		return panel;
  	}
  
  	/**
  	 * Redraw the list panels in the display according to which ones should be shown
  	 * @param inShowPhotos true to show photo list
  	 * @param inShowAudio true to show audio list
  	 */
  	private void redrawLists(boolean inShowPhotos, boolean inShowAudio)
  	{
  		// exit if same as last time
  		int panels = 1 + (inShowPhotos?2:0) + (inShowAudio?4:0);
  		if (panels == _visiblePanels) return;
  		_visiblePanels = panels;
  		// remove all panels and re-add them
  		_listsPanel.removeAll();
  		_listsPanel.setLayout(new GridLayout(0, 1));
  		_listsPanel.add(_waypointListPanel);
  		if (inShowPhotos) {
  			_listsPanel.add(_photoListPanel);
  		}
  		if (inShowAudio) {
  			_listsPanel.add(_audioListPanel);
  		}
  		_listsPanel.invalidate();
  		_listsPanel.getParent().validate();
  	}
  }
  