<commit_before>/*
 *    Debrief - the Open Source Maritime Analysis Application
 *    http://debrief.info
 *
 *    (C) 2000-2014, PlanetMayo Ltd
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the Eclipse Public License v1.0
 *    (http://www.eclipse.org/legal/epl-v10.html)
 *
 *    This library is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
 */
/**
 * 
 */
package org.mwc.debrief.core.editors;

import java.awt.Color;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

import org.eclipse.core.commands.Command;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.operations.IUndoableOperation;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.internal.utils.FileUtil;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFileState;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProduct;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.dialogs.IMessageProvider;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.TraverseEvent;
import org.eclipse.swt.events.TraverseListener;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.team.core.history.IFileRevision;
import org.eclipse.team.internal.core.history.LocalFileRevision;
import org.eclipse.team.internal.ui.history.FileRevisionEditorInput;
import org.eclipse.ui.IEditorDescriptor;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IPersistableElement;
import org.eclipse.ui.IPropertyListener;
import org.eclipse.ui.IViewPart;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.cheatsheets.OpenCheatSheetAction;
import org.eclipse.ui.commands.ICommandService;
import org.eclipse.ui.dialogs.ListDialog;
import org.eclipse.ui.dialogs.SaveAsDialog;
import org.eclipse.ui.handlers.HandlerUtil;
import org.eclipse.ui.ide.FileStoreEditorInput;
import org.eclipse.ui.ide.IGotoMarker;
import org.eclipse.ui.part.FileEditorInput;
import org.eclipse.ui.progress.WorkbenchJob;
import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
import org.eclipse.ui.views.navigator.ResourceNavigator;
import org.eclipse.ui.views.properties.IPropertySheetPage;
import org.mwc.cmap.core.CorePlugin;
import org.mwc.cmap.core.DataTypes.Temporal.ControllablePeriod;
import org.mwc.cmap.core.DataTypes.Temporal.ControllableTime;
import org.mwc.cmap.core.DataTypes.Temporal.TimeControlPreferences;
import org.mwc.cmap.core.DataTypes.Temporal.TimeControlProperties;
import org.mwc.cmap.core.DataTypes.Temporal.TimeManager;
import org.mwc.cmap.core.DataTypes.Temporal.TimeProvider;
import org.mwc.cmap.core.DataTypes.TrackData.TrackManager;
import org.mwc.cmap.core.interfaces.INamedItem;
import org.mwc.cmap.core.interfaces.TimeControllerOperation.TimeControllerOperationStore;
import org.mwc.cmap.core.property_support.RightClickSupport;
import org.mwc.cmap.gt2plot.proj.GtProjection;
import org.mwc.cmap.plotViewer.actions.Pan;
import org.mwc.cmap.plotViewer.actions.Pan.PanMode;
import org.mwc.cmap.plotViewer.actions.RangeBearing;
import org.mwc.cmap.plotViewer.actions.RangeBearing.RangeBearingMode;
import org.mwc.cmap.plotViewer.actions.ZoomIn;
import org.mwc.cmap.plotViewer.actions.ZoomIn.ZoomInMode;
import org.mwc.cmap.plotViewer.editors.chart.SWTCanvas;
import org.mwc.cmap.plotViewer.editors.chart.SWTChart;
import org.mwc.cmap.plotViewer.editors.chart.SWTChart.PlotMouseDragger;
import org.mwc.debrief.core.DebriefPlugin;
import org.mwc.debrief.core.ContextOperations.RainbowShadeSonarCuts.ShadeCutsOperation;
import org.mwc.debrief.core.ContextOperations.RainbowShadeSonarCuts.ShadeOperation;
import org.mwc.debrief.core.actions.DragComponent;
import org.mwc.debrief.core.actions.DragComponent.DragComponentMode;
import org.mwc.debrief.core.actions.DragFeature;
import org.mwc.debrief.core.actions.DragFeature.DragFeatureMode;
import org.mwc.debrief.core.actions.DragSegment;
import org.mwc.debrief.core.actions.DragSegment.DragSegmentMode;
import org.mwc.debrief.core.actions.RadioHandler;
import org.mwc.debrief.core.editors.painters.LayerPainterManager;
import org.mwc.debrief.core.interfaces.IPlotLoader;
import org.mwc.debrief.core.interfaces.IPlotLoader.BaseLoader;
import org.mwc.debrief.core.interfaces.IPlotLoader.DeferredPlotLoader;
import org.mwc.debrief.core.loaders.LoaderManager;
import org.mwc.debrief.core.loaders.ReplayLoader;
import org.mwc.debrief.core.loaders.xml_handlers.DebriefEclipseXMLReaderWriter;
import org.mwc.debrief.core.operations.ExportDopplerShift;
import org.mwc.debrief.core.operations.ExportTimeDataToClipboard;
import org.mwc.debrief.core.operations.ExportToFlatFile;
import org.mwc.debrief.core.operations.ExportToFlatFile2;
import org.mwc.debrief.core.operations.PlotOperations;
import org.mwc.debrief.core.preferences.PrefsPage;
import org.osgi.framework.Bundle;

import Debrief.GUI.Tote.Painters.SnailPainter;
import Debrief.ReaderWriter.Replay.ImportReplay;
import Debrief.Wrappers.NarrativeWrapper;
import Debrief.Wrappers.SensorContactWrapper;
import Debrief.Wrappers.SensorWrapper;
import Debrief.Wrappers.TrackWrapper;
import Debrief.Wrappers.Track.DynamicInfillSegment;
import Debrief.Wrappers.Track.RelativeTMASegment;
import Debrief.Wrappers.Track.TrackWrapper_Support.SegmentList;
import MWC.Algorithms.PlainProjection;
import MWC.Algorithms.PlainProjection.RelativeProjectionParent;
import MWC.GUI.BaseLayer;
import MWC.GUI.CanvasType;
import MWC.GUI.CreateEditorForParent;
import MWC.GUI.DynamicPlottable;
import MWC.GUI.Editable;
import MWC.GUI.HasEditables;
import MWC.GUI.Layer;
import MWC.GUI.Layers;
import MWC.GUI.Plottable;
import MWC.GenericData.HiResDate;
import MWC.GenericData.TimePeriod;
import MWC.GenericData.Watchable;
import MWC.GenericData.WatchableList;
import MWC.GenericData.WorldDistance;
import MWC.GenericData.WorldLocation;
import MWC.TacticalData.IRollingNarrativeProvider;
import MWC.TacticalData.TrackDataProvider;
import MWC.TacticalData.TrackDataProvider.TrackDataListener;

/**
 * @author ian.mayo
 */
@SuppressWarnings(
{"deprecation", "restriction"})
public class PlotEditor extends org.mwc.cmap.plotViewer.editors.CorePlotEditor
{
  // Extension point tag and attributes in plugin.xml
  private static final String EXTENSION_POINT_ID = "DebriefPlotLoader";

  private static final String EXTENSION_TAG = "loader";

  private static final String EXTENSION_TAG_LABEL_ATTRIB = "name";

  private static final String EXTENSION_TAG_EXTENSIONS_ATTRIB = "extensions";

  private static final String EXTENSION_TAG_FIRST_LINE_ATTRIB = "first_line";

  private static final String EXTENSION_TAG_ICON_ATTRIB = "icon";

  // private static final String EXTENSION_TAG_CLASS_ATTRIB = "class";

  // Plug-in ID from <plugin> tag in plugin.xml
  private static final String PLUGIN_ID = "org.mwc.debrief.core";

  /**
   * helper object which loads plugin file-loaders
   */
  LoaderManager _loader;

  /**
   * we keep the reference to our track-type adapter
   */
  TrackDataProvider _trackDataProvider;

  /**
   * The job used to handle large changes in layers
   */
  private Job _refreshJob;

  /**
   * something to look after our layer painters
   */
  LayerPainterManager _layerPainterManager;

  /**
   * and how we view the time
   * 
   */
  protected TimeControlPreferences _timePreferences;

  private PlotOperations _myOperations;

  /**
   * support tool that provides a relative plot
   */
  private RelativeProjectionParent _myRelativeWrapper;

  /**
   * handle narrative management
   */
  protected IRollingNarrativeProvider _theNarrativeProvider;

  /**
   * an object to look after all of the time bits
   */
  private TimeManager _timeManager;

  private org.mwc.cmap.core.interfaces.TimeControllerOperation.TimeControllerOperationStore _timeControllerOperations;

  private PlotOutlinePage _outlinePage;

  private TraverseListener dragModeListener = new TraverseListener()
  {

    @Override
    public void keyTraversed(TraverseEvent e)
    {
      if (getChart() == null)
      {
        return;
      }
      Control control = getChart().getCanvasControl();
      if (control == null || control.isDisposed() || !control.isVisible())
      {
        return;
      }
      if (e.detail == SWT.TRAVERSE_TAB_NEXT)
      {
        PlotMouseDragger dragMode = getChart().getDragMode();
        if (dragMode != null)
        {
          try
          {
            String currentState = null;
            ExecutionEvent executionEvent = new ExecutionEvent();
            if (dragMode instanceof DragSegmentMode)
            {
              new DragComponent().execute(executionEvent);
              currentState = RadioHandler.DRAG_COMPONENT;
            }
            else if (dragMode instanceof DragComponentMode)
            {
              new DragFeature().execute(executionEvent);
              currentState = RadioHandler.DRAG_FEATURE;
            }
            else if (dragMode instanceof DragFeatureMode)
            {
              new RangeBearing().execute(executionEvent);
              currentState = RadioHandler.RANGE_BEARING;
            }
            else if (dragMode instanceof RangeBearingMode)
            {
              new Pan().execute(executionEvent);
              currentState = RadioHandler.PAN;
            }
            else if (dragMode instanceof PanMode)
            {
              new ZoomIn().execute(executionEvent);
              currentState = RadioHandler.ZOOM_IN;
            }
            else if (dragMode instanceof ZoomInMode)
            {
              new DragSegment().execute(executionEvent);
              currentState = RadioHandler.DRAG_SEGMENT;
            }
            if (currentState != null)
            {
              ICommandService service =
                  (ICommandService) getSite().getService(ICommandService.class);
              Command command = service.getCommand(RadioHandler.ID);
              HandlerUtil.updateRadioState(command, currentState);
            }
          }
          catch (Exception e1)
          {
            CorePlugin.logError(Status.WARNING, "Cannot change drag mode:", e1);
          }
        }
      }
    }
  };

  private PlotPropertySheetPage _propertySheetPage;

  /**
   * constructor - quite simple really.
   */
  public PlotEditor()
  {
    super();

    // create the track manager to manage the primary & secondary tracks
    _trackDataProvider = new TrackManager(_myLayers);

    // and listen out form modifications, because we want to mark ourselves
    // as
    // dirty once they've updated
    _trackDataProvider.addTrackDataListener(new TrackDataListener()
    {
      public void tracksUpdated(final WatchableList primary,
          final WatchableList[] secondaries)
      {
        fireDirty();
      }
    });

    // sort out the time controlleroperations
    _timeControllerOperations =
        new org.mwc.cmap.core.interfaces.TimeControllerOperation.TimeControllerOperationStore();
    _timeControllerOperations.add(new ExportTimeDataToClipboard());
    _timeControllerOperations.add(new ExportToFlatFile());
    _timeControllerOperations.add(new ExportToFlatFile2());
    _timeControllerOperations.add(new ExportDopplerShift());

    _layerPainterManager = new LayerPainterManager(_trackDataProvider);
    _layerPainterManager.addPropertyChangeListener(new PropertyChangeListener()
    {
      public void propertyChange(final PropertyChangeEvent arg0)
      {
        // ok, trigger repaint of plot
        if (getChart() != null)
          getChart().update();
      }
    });

    // create the time manager. cool
    _timeManager = new TimeManager();
    _timeManager.addListener(_timeListener,
        TimeProvider.TIME_CHANGED_PROPERTY_NAME);

    // and how time is managed
    _timePreferences = new TimeControlProperties();

    // listen out for when our input changes, since we will change the
    // editor
    // window title
    this.addPropertyListener(new IPropertyListener()
    {

      @SuppressWarnings(
      {"synthetic-access"})
      public void propertyChanged(final Object source, final int propId)
      {
        if (propId == PROP_INPUT)
        {
          final Object input = getEditorInput();
          if (input instanceof IFileEditorInput)
          {
            final IFileEditorInput inp = (IFileEditorInput) getEditorInput();
            setPartName(inp.getName());
          }
          else if (input instanceof FileStoreEditorInput)
          {
            final FileStoreEditorInput fsi = (FileStoreEditorInput) input;
            final String theName = fsi.getName();
            setPartName(theName);
          }
          else if (input instanceof FileRevisionEditorInput)
          {
            setPartName(((FileRevisionEditorInput) input).getName());
          }
          else
          {
            CorePlugin.logError(Status.WARNING,
                "data source for PlotEditor not of expected type:" + input,
                null);
            setPartName(input.toString());
          }
        }
      }
    });

    _myOperations = new PlotOperations()
    {
      // just provide with our complete set of layers
      @SuppressWarnings("synthetic-access")
      public Object[] getTargets()
      {
        // ok, return our top level layers as objects
        final Vector<Layer> res = new Vector<Layer>(0, 1);
        for (int i = 0; i < _myLayers.size(); i++)
        {
          res.add(_myLayers.elementAt(i));
        }
        return res.toArray();
      }

      /**
       * override performing the operation, since we'll do a screen update on completion
       */
      @SuppressWarnings("synthetic-access")
      public Vector<Layer> performOperation(final AnOperation operationName)
      {
        // make the actual change
        final Vector<Layer> res = super.performOperation(operationName);

        if (res != null)
        {
          if (res.size() != 0)
          {
            for (final Iterator<Layer> iter = res.iterator(); iter.hasNext();)
            {
              final Layer thisL = (Layer) iter.next();
              // and update the screen
              _myLayers.fireReformatted(thisL);

            }
          }
        }

        return res;

      }
    };

    // do we have some time preferences?
    if (_timePreferences != null)
    {
      final HiResDate startDTG = _timePreferences.getSliderStartTime();
      final HiResDate endDTG = _timePreferences.getSliderEndTime();
      // and were there any times in it?
      if ((startDTG != null) && (endDTG != null))
      {
        // yup, store the time data.
        _myOperations
            .setPeriod(new TimePeriod.BaseTimePeriod(startDTG, endDTG));
      }
    }

    // introduce our new item listener that looks after relative TMA segments being dropped into the
    // layers object
    _myLayers.addDataExtendedListener(new Layers.DataListener2()
    {

      @Override
      public void dataModified(Layers theData, Layer changedLayer)
      {
      }

      @Override
      public void dataExtended(Layers theData)
      {
      }

      @Override
      public void dataReformatted(Layers theData, Layer changedLayer)
      {
      }

      @Override
      public void dataExtended(final Layers theData, final Plottable newItem,
          final HasEditables parent)
      {
        reconnectSegments(newItem, parent);
      }
    });
  }

  @Override
  public void dispose()
  {
    super.dispose();

    // stop listening to the time manager
    _timeManager.removeListener(_timeListener,
        TimeProvider.TIME_CHANGED_PROPERTY_NAME);

    _timeManager = null;

    if (_layerPainterManager != null)
    {
      _layerPainterManager.close();
      _layerPainterManager = null;
    }

    if (_outlinePage != null)
    {
      _outlinePage.dispose();
      _outlinePage = null;
    }
  }

  public void init(final IEditorSite site, final IEditorInput input)
      throws PartInitException
  {
    setSite(site);
    setInputWithNotify(input);

    // ok - declare and load the supplemental plugins which can load
    // datafiles
    initialiseFileLoaders();

    // and start the load
    loadThisFile(input);

    // lastly, set the title (if we have one)
    this.setPartName(input.getName());

    // hmm, does this input have an icon?
    ImageDescriptor icon = input.getImageDescriptor();
    if (icon != null)
    {
      this.setTitleImage(icon.createImage());
    }

    // ok, also sort out that refresh job
    createRefreshJob();
  }

  /**
   * @param input
   *          the file to insert
   */
  private void loadThisFile(IEditorInput input)
  {
    InputStream is = null;
    if (!input.exists())
    {
      CorePlugin.logError(Status.ERROR, "File cannot be found:"
          + input.getName(), null);
      return;
    }
    String name = input.getName();
    try
    {
      final IPersistableElement persist = input.getPersistable();
      if (input instanceof IFileEditorInput)
      {
        final IFileEditorInput ife = (IFileEditorInput) input;
        final IFile iff = ife.getFile();
        iff.refreshLocal(IResource.DEPTH_ONE, null);
        is = iff.getContents();
        name = getAbsoluteName(iff);
      }
      else if (persist instanceof IFileEditorInput)
      {
        final IFileEditorInput ifi = (IFileEditorInput) persist;
        IFile iff = ifi.getFile();
        is = iff.getContents();
        name = getAbsoluteName(iff);
      }
      else if (input instanceof FileStoreEditorInput)
      {
        final FileStoreEditorInput _input = (FileStoreEditorInput) input;
        final URI _uri = _input.getURI();
        final Path _p = new Path(_uri.getPath());
        name = _uri.getPath();
        final IFileStore _ifs = EFS.getLocalFileSystem().getStore(_p);
        is = _ifs.openInputStream(EFS.NONE, null);
      }
      else if (input instanceof FileRevisionEditorInput)
      {
        final FileRevisionEditorInput frei = (FileRevisionEditorInput) input;
        IFile file = getFile(frei);
        if (file != null && file.exists())
        {
          name = getAbsoluteName(file);
          is = frei.getStorage().getContents();
        }
      }
      if (is != null)
        loadThisStream(is, name);
      else
      {
        CorePlugin.logError(Status.INFO, "Failed to load file from:" + input,
            null);
      }

    }
    catch (final CoreException e)
    {
      CorePlugin.logError(Status.ERROR, "Resource out of sync:"
          + input.getName() + " REFRESH the workspace", null);
      MessageDialog
          .openError(
              Display.getDefault().getActiveShell(),
              "File out of sync",
              "This file has been edited or removed:"
                  + input.getName()
                  + "\nPlease right-click on your navigator project and press Refresh");
    }
  }

  private IFile getFile(FileRevisionEditorInput frei)
  {
    IFile file = null;
    IFileRevision revision = frei.getFileRevision();
    if (revision instanceof LocalFileRevision)
    {
      LocalFileRevision localFileRevision = (LocalFileRevision) revision;
      if (localFileRevision.getFile() != null)
      {
        file = localFileRevision.getFile();
      }
      else
      {
        IFileState state = localFileRevision.getState();
        IPath path = state.getFullPath();
        file = ResourcesPlugin.getWorkspace().getRoot().getFile(path);
      }
    }
    return file;
  }

  public String getAbsoluteName(final IFile iff) throws CoreException
  {
    String name;
    URI uri = iff.getLocationURI();
    if (iff.isLinked())
    {
      uri = iff.getRawLocationURI();
    }
    File javaFile = EFS.getStore(uri).toLocalFile(0, new NullProgressMonitor());
    name = javaFile.getAbsolutePath();
    return name;
  }

  /**
   * @param input
   *          the file to insert
   */
  private void loadThisFile(final String filePath)
  {
    FileInputStream ifs = null;
    try
    {
      ifs = new FileInputStream(filePath);
      loadThisStream(ifs, filePath);
    }
    catch (final FileNotFoundException e)
    {
      CorePlugin.logError(Status.ERROR,
          "Problem loading data file:" + filePath, e);
    }
    finally
    {
      if (ifs != null)
        try
        {
          ifs.close();
        }
        catch (IOException e)
        {
          CorePlugin.logError(Status.ERROR, "Problem closing input stream:"
              + filePath, e);
        }
    }
  }

  private void loadThisStream(final InputStream is, final String fileName)
  {
    // right, see if any of them will do our edit
    final IPlotLoader[] loaders = _loader.findLoadersFor(fileName);
    // did we find any?
    if (loaders.length > 0)
    {
      // cool, give them a go...
      try
      {
        for (int i = 0; i < loaders.length; i++)
        {
          final IPlotLoader thisLoader = loaders[i];

          // get it to load. Just in case it's an asychronous load
          // operation, we
          // rely on it calling us back (loadingComplete)
          thisLoader.loadFile(this, is, fileName);

          // special handling - popup a dialog to allow sensor name/color to be
          // set if there's just one sensor
          if (thisLoader instanceof DeferredPlotLoader)
          {
            final DeferredPlotLoader ld = (DeferredPlotLoader) thisLoader;
            final BaseLoader loader = ld.getLoader();
            if (loader != null)
            {
              if (loader instanceof ReplayLoader)
              {
                final ReplayLoader rl = (ReplayLoader) loader;
                final ImportReplay ir = rl.getReplayLoader();
                List<TrackWrapper> candidateHosts = null;

                final Vector<SensorWrapper> sensors = ir.getPendingSensors();

                // see if there are any sensors awaiting a host
                if (sensors.size() > 1)
                {
                  Iterator<SensorWrapper> sIter = sensors.iterator();
                  while (sIter.hasNext())
                  {
                    SensorWrapper sensor = (SensorWrapper) sIter.next();
                    if (sensor.getHost() == null)
                    {
                      // have we sorted out the hosts?
                      if (candidateHosts == null)
                      {
                        candidateHosts = determineCandidateHosts();
                      }

                      if (candidateHosts.size() == 0)
                      {
                        CorePlugin.showMessage("Loading sensor data",
                            "Sensor data can only be loaded after tracks");
                        return;
                      }

                      // ok, let the user choose
                      chooseHostFor(sensor, candidateHosts);
                    }
                  }
                }
                else if (sensors.size() == 1)
                {
                  // see if there are any sensors awaiting a color
                  final SensorWrapper thisS = sensors.firstElement();
                  final boolean success = nameThisSensor(thisS);
                  
                  // does user wish to name/format sensor?
                  if(!success)
                  {
                    // nope, cancel the import.
                    ir.clearPendingSensorList();
                  }
                }

                // ok, now we can store the pending sensors in their
                // parent tracks
                ir.storePendingSensors();
              }
            }
          }
        }
      }
      catch (final RuntimeException e)
      {
        CorePlugin.logError(Status.ERROR, "Problem loading data file:"
            + fileName, e);
      }
    }
  }

  private List<TrackWrapper> determineCandidateHosts()
  {
    List<TrackWrapper> res = new ArrayList<TrackWrapper>();

    Enumeration<Editable> iter = _myLayers.elements();
    while (iter.hasMoreElements())
    {
      Editable editable = (Editable) iter.nextElement();
      if (editable instanceof TrackWrapper)
      {
        res.add((TrackWrapper) editable);
      }
    }

    return res;
  }

  private void chooseHostFor(SensorWrapper sensor,
      List<TrackWrapper> candidateHosts)
  {
    // ok, construct the popup
    Object[] tArr = candidateHosts.toArray();

    // popup the layers in a question dialog
    final IStructuredContentProvider theVals = new ArrayContentProvider();
    final ILabelProvider theLabels = new LabelProvider();

    // collate the dialog
    final ListDialog list =
        new ListDialog(Display.getCurrent().getActiveShell());
    list.setContentProvider(theVals);
    list.setLabelProvider(theLabels);
    list.setInput(tArr);
    list.setMessage("Please select the track for sensor titled \""
        + sensor.getName() + "\"\n(or Cancel to not store it)");
    list.setTitle("Sensor track not found");
    list.setHelpAvailable(false);

    // select the first item, so it's valid to press OK immediately
    list.setInitialSelections(new Object[]
    {tArr[0]});

    // open it
    final int selection = list.open();

    // did user say yes?
    if (selection != ListDialog.CANCEL)
    {
      // yup, store it's name
      final Object[] val = list.getResult();

      // check something got selected
      if (val.length == 1)
      {
        TrackWrapper selected = (TrackWrapper) val[0];
        sensor.setHost(selected);
      }
    }
  }

  private boolean nameThisSensor(final SensorWrapper thisS)
  {
    // right, just have a quick look and see if the sensor has range data -
    // because
    // if it doesn't we'll let the user set a default
    final boolean needsRange = areWeWaitingForRange(thisS);

    // next, just see if this track already contains sensor
    // data with this name
    final boolean alreadyLoaded = isThisSensorAlreadyPresent(thisS);

    // inform the user if this sensor name is already in use
    final String introString;
    if (alreadyLoaded)
    {
      introString =
          "a one-word title for this block of sensor contacts (e.g. S2046)\n\n"
              + "Note: [" + thisS.getName() + "] is already in use.";
    }
    else
    {
      introString =
          "a one-word title for this block of sensor contacts (e.g. S2046)";
    }

    SensorImportHelper importHelper =
        getSensorImportHelperFor(thisS.getName(), thisS.getColor(), introString, needsRange);

    // did it work?
    if (importHelper.success())
    {
      // ok, use the name
      thisS.setName(importHelper.getName());
      thisS.setColor(importHelper.getColor());
      thisS.setVisible(importHelper.getVisiblity());

      // are we doing range?
      if (needsRange)
      {
        final WorldDistance theRange = importHelper.getRange();

        // did a range get entered?
        if ((theRange != null) && (theRange.getValue() != 0))
        {
          final Enumeration<Editable> iter = thisS.elements();
          while (iter.hasMoreElements())
          {
            final SensorContactWrapper cut =
                (SensorContactWrapper) iter.nextElement();
            cut.setRange(new WorldDistance(theRange));
          }
        }
      }
      if (importHelper.applyRainbow())
      {
        applyRainbowShadingTo(thisS);
      }
    }
    
    return importHelper.success();
  }

  private boolean areWeWaitingForRange(final SensorWrapper thisS)
  {
    boolean needsRange = false;
    final Enumeration<Editable> cuts = thisS.elements();
    if (cuts.hasMoreElements())
    {
      final Editable firstCut = cuts.nextElement();
      final SensorContactWrapper scw = (SensorContactWrapper) firstCut;
      // do we have bearing?
      if (scw.getHasBearing())
      {
        // yes. now are we waiting for a range?
        if (scw.getRange() == null)
        {
          needsRange = true;
        }
      }
    }
    return needsRange;
  }

  private boolean isThisSensorAlreadyPresent(final SensorWrapper thisS)
  {
    boolean alreadyLoaded = false;
    final TrackWrapper trk = thisS.getHost();
    if (trk != null)
    {
      final Enumeration<Editable> enumer = trk.getSensors().elements();
      while (enumer.hasMoreElements())
      {
        final SensorWrapper oldS = (SensorWrapper) enumer.nextElement();
        if (oldS.getName().equals(thisS.getName()))
        {
          alreadyLoaded = true;
        }
      }
    }
    return alreadyLoaded;
  }

  private SensorImportHelper getSensorImportHelperFor(String sensorName, Color sensorColor,
      String introString, boolean needsRange)
  {
    // ok, check the property
    final String showImportWizard =
        CorePlugin.getToolParent().getProperty(
            PrefsPage.PreferenceConstants.USE_IMPORT_SENSOR_WIZARD);

    // create the relevant helper
    final SensorImportHelper helper;
    if (Boolean.parseBoolean(showImportWizard))
    {
      helper =
          new SensorImportHelper.SensorImportHelperUI(sensorName, sensorColor,
              introString, needsRange);
    }
    else
    {
      helper = new SensorImportHelper.SensorImportHelperHeadless(sensorName);
    }

    return helper;
  }

  private void applyRainbowShadingTo(final SensorWrapper thisS)
  {
    SensorWrapper theSensor = null;

    // are they items we're interested in?
    HiResDate startDTG = new HiResDate(Long.MAX_VALUE / 1000, 0);
    HiResDate endDTG = new HiResDate(0);
    Enumeration<Editable> elements = thisS.elements();
    ArrayList<Editable> sensors = new ArrayList<Editable>();
    ArrayList<SensorContactWrapper> list =
        new ArrayList<SensorContactWrapper>();
    while (elements.hasMoreElements())
    {
      sensors.add(elements.nextElement());
    }
    for (Editable thisE : sensors)
    {
      if (thisE instanceof SensorWrapper)
      {
        // just check that there's only one item selected
        if (sensors.size() == 1)
        {
          theSensor = (SensorWrapper) thisE;
        }
      }
      else if (thisE instanceof SensorContactWrapper)
      {
        list.add((SensorContactWrapper) thisE);
        if (startDTG.compareTo(((SensorContactWrapper) thisE).getDTG()) > 0)
        {
          startDTG = ((SensorContactWrapper) thisE).getDTG();
        }
        if (endDTG.compareTo(((SensorContactWrapper) thisE).getDTG()) < 0)
        {
          endDTG = ((SensorContactWrapper) thisE).getDTG();
        }
      }
    }

    // ok, do we have a single sensor?
    if (theSensor != null)
    {
      startDTG = theSensor.getStartDTG();
      endDTG = theSensor.getEndDTG();
      Collection<Editable> editables =
          theSensor.getItemsBetween(theSensor.getStartDTG(), theSensor
              .getEndDTG());
      for (Editable editable : editables)
      {
        if (editable instanceof SensorContactWrapper)
        {
          list.add((SensorContactWrapper) editable);
        }
      }
    }
    final HiResDate start = startDTG;
    final HiResDate end = endDTG;
    // create this operation
    final String title1 = "Shade in rainbow colors";
    Layer parentLayer = null;

    Layers parentLayers = _myLayers;
    if (parentLayers != null)
    {
      if (parentLayers.size() == 1)
      {
        parentLayer = parentLayers.elementAt(0);
      }
    }

    final IUndoableOperation theAction =
        new ShadeCutsOperation(title1, parentLayers, parentLayer, list
            .toArray(new SensorContactWrapper[0]), start, end,
            ShadeOperation.RAINBOW_SHADE);
    CorePlugin.run(theAction);
  }

  /**
	 * 
	 */
  private void initialiseFileLoaders()
  {
    // hey - sort out our plot readers
    _loader = new LoaderManager(EXTENSION_POINT_ID, EXTENSION_TAG, PLUGIN_ID)
    {

      public INamedItem createInstance(
          final IConfigurationElement configElement, final String label)
      {
        // get the attributes
        final String theLabel =
            configElement.getAttribute(EXTENSION_TAG_LABEL_ATTRIB);
        final String icon =
            configElement.getAttribute(EXTENSION_TAG_ICON_ATTRIB);
        final String fileTypes =
            configElement.getAttribute(EXTENSION_TAG_EXTENSIONS_ATTRIB);
        final String firstLine =
            configElement.getAttribute(EXTENSION_TAG_FIRST_LINE_ATTRIB);

        // create the instance
        final INamedItem res =
            new IPlotLoader.DeferredPlotLoader(configElement, theLabel, icon,
                fileTypes, firstLine);

        // and return it.
        return res;
      }

    };
  }

  private static TimePeriod getPeriodFor(final Layers theData)
  {
    TimePeriod res = null;

    for (final Enumeration<Editable> iter = theData.elements(); iter
        .hasMoreElements();)
    {
      final Layer thisLayer = (Layer) iter.nextElement();

      // and through this layer
      if (thisLayer instanceof TrackWrapper)
      {
        final TrackWrapper thisT = (TrackWrapper) thisLayer;
        res = extend(res, thisT.getStartDTG());
        res = extend(res, thisT.getEndDTG());
      }
      else if (thisLayer instanceof BaseLayer)
      {
        final Enumeration<Editable> elements = thisLayer.elements();
        while (elements.hasMoreElements())
        {
          final Plottable nextP = (Plottable) elements.nextElement();
          if (nextP instanceof Watchable)
          {
            final Watchable wrapped = (Watchable) nextP;
            final HiResDate dtg = wrapped.getTime();
            if (dtg != null)
            {
              res = extend(res, dtg);

              // also see if it this data type an end time
              if (wrapped instanceof WatchableList)
              {
                // ok, make sure we also handle the end time
                final WatchableList wl = (WatchableList) wrapped;
                final HiResDate endD = wl.getEndDTG();
                if (endD != null)
                  res = extend(res, endD);
              }
            }
          }
        }
      }
    }

    return res;
  }

  private static TimePeriod
      extend(final TimePeriod period, final HiResDate date)
  {
    TimePeriod result = period;
    // have we received a date?
    if (date != null)
    {
      if (result == null)
      {
        result = new TimePeriod.BaseTimePeriod(date, date);
      }
      else
        result.extend(date);
    }

    return result;
  }

  /**
   * method called when a helper object has completed a plot-load operation
   * 
   * @param source
   */
  public void loadingComplete(final Object source)
  {

    // ok, stop listening for dirty calls - since there will be so many and
    // we
    // don't want
    // to start off with a dirty plot
    startIgnoringDirtyCalls();

    DebriefPlugin.logError(Status.INFO, "File loading complete received", null);

    // and update the time management bits
    final TimePeriod timePeriod = getPeriodFor(_myLayers);

    if (timePeriod != null)
    {
      _timeManager.setPeriod(this, timePeriod);

      // also give it a current DTG (if it doesn't have one)
      if (_timeManager.getTime() == null)
        _timeManager.setTime(this, timePeriod.getStartDTG(), false);
    }

    // done - now we can process dirty calls again
    stopIgnoringDirtyCalls();

  }

  protected void filesDropped(final String[] fileNames)
  {
    super.filesDropped(fileNames);

    // ok, iterate through the files
    for (int i = 0; i < fileNames.length; i++)
    {
      final String thisFilename = fileNames[i];
      loadThisFile(thisFilename);
    }

    // ok, we're probably done - fire the update
    this._myLayers.fireExtended();

    // and resize to make sure we're showing all the data
    this._myChart.rescale();

    // hmm, we may have loaded more track data - but we don't track
    // loading of individual tracks - just fire a "modified" flag
    _trackDataProvider.fireTracksChanged();

  }

  private static boolean _updatingPlot = false;

  /*
   * (non-Javadoc)
   * 
   * @see org.mwc.cmap.plotViewer.editors.CorePlotEditor#timeChanged()
   */
  protected void timeChanged(final HiResDate newDTG)
  {
    super.timeChanged(newDTG);

    // just check we're ready for plotting.
    if (getChart() == null)
      return;

    if (_updatingPlot)
    {
      // skip the update - we're already at it
    }
    else
    {
      _updatingPlot = true;

      try
      {
        // note, we've learn't to use the default display instead of the
        // current one, we were get a null returned since this thread may not<commit_msg>Workaround for missing object model entries<commit_after>/*
 *    Debrief - the Open Source Maritime Analysis Application
 *    http://debrief.info
 *
 *    (C) 2000-2014, PlanetMayo Ltd
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the Eclipse Public License v1.0
 *    (http://www.eclipse.org/legal/epl-v10.html)
 *
 *    This library is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
 */
/**
 * 
 */
package org.mwc.debrief.core.editors;

import java.awt.Color;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

import org.eclipse.core.commands.Command;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.operations.IUndoableOperation;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.internal.utils.FileUtil;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFileState;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProduct;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.dialogs.IMessageProvider;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.TraverseEvent;
import org.eclipse.swt.events.TraverseListener;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.team.core.history.IFileRevision;
import org.eclipse.team.internal.core.history.LocalFileRevision;
import org.eclipse.team.internal.ui.history.FileRevisionEditorInput;
import org.eclipse.ui.IEditorDescriptor;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IPersistableElement;
import org.eclipse.ui.IPropertyListener;
import org.eclipse.ui.IViewPart;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.cheatsheets.OpenCheatSheetAction;
import org.eclipse.ui.commands.ICommandService;
import org.eclipse.ui.dialogs.ListDialog;
import org.eclipse.ui.dialogs.SaveAsDialog;
import org.eclipse.ui.handlers.HandlerUtil;
import org.eclipse.ui.ide.FileStoreEditorInput;
import org.eclipse.ui.ide.IGotoMarker;
import org.eclipse.ui.part.FileEditorInput;
import org.eclipse.ui.progress.WorkbenchJob;
import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
import org.eclipse.ui.views.navigator.ResourceNavigator;
import org.eclipse.ui.views.properties.IPropertySheetPage;
import org.mwc.cmap.core.CorePlugin;
import org.mwc.cmap.core.DataTypes.Temporal.ControllablePeriod;
import org.mwc.cmap.core.DataTypes.Temporal.ControllableTime;
import org.mwc.cmap.core.DataTypes.Temporal.TimeControlPreferences;
import org.mwc.cmap.core.DataTypes.Temporal.TimeControlProperties;
import org.mwc.cmap.core.DataTypes.Temporal.TimeManager;
import org.mwc.cmap.core.DataTypes.Temporal.TimeProvider;
import org.mwc.cmap.core.DataTypes.TrackData.TrackManager;
import org.mwc.cmap.core.interfaces.INamedItem;
import org.mwc.cmap.core.interfaces.TimeControllerOperation.TimeControllerOperationStore;
import org.mwc.cmap.core.property_support.EditableWrapper;
import org.mwc.cmap.core.property_support.RightClickSupport;
import org.mwc.cmap.gt2plot.proj.GtProjection;
import org.mwc.cmap.plotViewer.actions.Pan;
import org.mwc.cmap.plotViewer.actions.Pan.PanMode;
import org.mwc.cmap.plotViewer.actions.RangeBearing;
import org.mwc.cmap.plotViewer.actions.RangeBearing.RangeBearingMode;
import org.mwc.cmap.plotViewer.actions.ZoomIn;
import org.mwc.cmap.plotViewer.actions.ZoomIn.ZoomInMode;
import org.mwc.cmap.plotViewer.editors.chart.SWTCanvas;
import org.mwc.cmap.plotViewer.editors.chart.SWTChart;
import org.mwc.cmap.plotViewer.editors.chart.SWTChart.PlotMouseDragger;
import org.mwc.debrief.core.DebriefPlugin;
import org.mwc.debrief.core.ContextOperations.RainbowShadeSonarCuts.ShadeCutsOperation;
import org.mwc.debrief.core.ContextOperations.RainbowShadeSonarCuts.ShadeOperation;
import org.mwc.debrief.core.actions.DragComponent;
import org.mwc.debrief.core.actions.DragComponent.DragComponentMode;
import org.mwc.debrief.core.actions.DragFeature;
import org.mwc.debrief.core.actions.DragFeature.DragFeatureMode;
import org.mwc.debrief.core.actions.DragSegment;
import org.mwc.debrief.core.actions.DragSegment.DragSegmentMode;
import org.mwc.debrief.core.actions.RadioHandler;
import org.mwc.debrief.core.editors.painters.LayerPainterManager;
import org.mwc.debrief.core.interfaces.IPlotLoader;
import org.mwc.debrief.core.interfaces.IPlotLoader.BaseLoader;
import org.mwc.debrief.core.interfaces.IPlotLoader.DeferredPlotLoader;
import org.mwc.debrief.core.loaders.LoaderManager;
import org.mwc.debrief.core.loaders.ReplayLoader;
import org.mwc.debrief.core.loaders.xml_handlers.DebriefEclipseXMLReaderWriter;
import org.mwc.debrief.core.operations.ExportDopplerShift;
import org.mwc.debrief.core.operations.ExportTimeDataToClipboard;
import org.mwc.debrief.core.operations.ExportToFlatFile;
import org.mwc.debrief.core.operations.ExportToFlatFile2;
import org.mwc.debrief.core.operations.PlotOperations;
import org.mwc.debrief.core.preferences.PrefsPage;
import org.osgi.framework.Bundle;

import Debrief.GUI.Tote.Painters.SnailPainter;
import Debrief.ReaderWriter.Replay.ImportReplay;
import Debrief.Wrappers.FixWrapper;
import Debrief.Wrappers.NarrativeWrapper;
import Debrief.Wrappers.SensorContactWrapper;
import Debrief.Wrappers.SensorWrapper;
import Debrief.Wrappers.TMAContactWrapper;
import Debrief.Wrappers.TMAWrapper;
import Debrief.Wrappers.TrackWrapper;
import Debrief.Wrappers.Track.DynamicInfillSegment;
import Debrief.Wrappers.Track.RelativeTMASegment;
import Debrief.Wrappers.Track.TrackSegment;
import Debrief.Wrappers.Track.TrackWrapper_Support.SegmentList;
import MWC.Algorithms.PlainProjection;
import MWC.Algorithms.PlainProjection.RelativeProjectionParent;
import MWC.GUI.BaseLayer;
import MWC.GUI.CanvasType;
import MWC.GUI.CreateEditorForParent;
import MWC.GUI.DynamicPlottable;
import MWC.GUI.Editable;
import MWC.GUI.HasEditables;
import MWC.GUI.Layer;
import MWC.GUI.Layers;
import MWC.GUI.Plottable;
import MWC.GenericData.HiResDate;
import MWC.GenericData.TimePeriod;
import MWC.GenericData.Watchable;
import MWC.GenericData.WatchableList;
import MWC.GenericData.WorldDistance;
import MWC.GenericData.WorldLocation;
import MWC.TacticalData.IRollingNarrativeProvider;
import MWC.TacticalData.TrackDataProvider;
import MWC.TacticalData.TrackDataProvider.TrackDataListener;

/**
 * @author ian.mayo
 */
@SuppressWarnings(
{"deprecation", "restriction"})
public class PlotEditor extends org.mwc.cmap.plotViewer.editors.CorePlotEditor
{
  // Extension point tag and attributes in plugin.xml
  private static final String EXTENSION_POINT_ID = "DebriefPlotLoader";

  private static final String EXTENSION_TAG = "loader";

  private static final String EXTENSION_TAG_LABEL_ATTRIB = "name";

  private static final String EXTENSION_TAG_EXTENSIONS_ATTRIB = "extensions";

  private static final String EXTENSION_TAG_FIRST_LINE_ATTRIB = "first_line";

  private static final String EXTENSION_TAG_ICON_ATTRIB = "icon";

  // private static final String EXTENSION_TAG_CLASS_ATTRIB = "class";

  // Plug-in ID from <plugin> tag in plugin.xml
  private static final String PLUGIN_ID = "org.mwc.debrief.core";

  /**
   * helper object which loads plugin file-loaders
   */
  LoaderManager _loader;

  /**
   * we keep the reference to our track-type adapter
   */
  TrackDataProvider _trackDataProvider;

  /**
   * The job used to handle large changes in layers
   */
  private Job _refreshJob;

  /**
   * something to look after our layer painters
   */
  LayerPainterManager _layerPainterManager;

  /**
   * and how we view the time
   * 
   */
  protected TimeControlPreferences _timePreferences;

  private PlotOperations _myOperations;

  /**
   * support tool that provides a relative plot
   */
  private RelativeProjectionParent _myRelativeWrapper;

  /**
   * handle narrative management
   */
  protected IRollingNarrativeProvider _theNarrativeProvider;

  /**
   * an object to look after all of the time bits
   */
  private TimeManager _timeManager;

  private org.mwc.cmap.core.interfaces.TimeControllerOperation.TimeControllerOperationStore _timeControllerOperations;

  private PlotOutlinePage _outlinePage;

  private TraverseListener dragModeListener = new TraverseListener()
  {

    @Override
    public void keyTraversed(TraverseEvent e)
    {
      if (getChart() == null)
      {
        return;
      }
      Control control = getChart().getCanvasControl();
      if (control == null || control.isDisposed() || !control.isVisible())
      {
        return;
      }
      if (e.detail == SWT.TRAVERSE_TAB_NEXT)
      {
        PlotMouseDragger dragMode = getChart().getDragMode();
        if (dragMode != null)
        {
          try
          {
            String currentState = null;
            ExecutionEvent executionEvent = new ExecutionEvent();
            if (dragMode instanceof DragSegmentMode)
            {
              new DragComponent().execute(executionEvent);
              currentState = RadioHandler.DRAG_COMPONENT;
            }
            else if (dragMode instanceof DragComponentMode)
            {
              new DragFeature().execute(executionEvent);
              currentState = RadioHandler.DRAG_FEATURE;
            }
            else if (dragMode instanceof DragFeatureMode)
            {
              new RangeBearing().execute(executionEvent);
              currentState = RadioHandler.RANGE_BEARING;
            }
            else if (dragMode instanceof RangeBearingMode)
            {
              new Pan().execute(executionEvent);
              currentState = RadioHandler.PAN;
            }
            else if (dragMode instanceof PanMode)
            {
              new ZoomIn().execute(executionEvent);
              currentState = RadioHandler.ZOOM_IN;
            }
            else if (dragMode instanceof ZoomInMode)
            {
              new DragSegment().execute(executionEvent);
              currentState = RadioHandler.DRAG_SEGMENT;
            }
            if (currentState != null)
            {
              ICommandService service =
                  (ICommandService) getSite().getService(ICommandService.class);
              Command command = service.getCommand(RadioHandler.ID);
              HandlerUtil.updateRadioState(command, currentState);
            }
          }
          catch (Exception e1)
          {
            CorePlugin.logError(Status.WARNING, "Cannot change drag mode:", e1);
          }
        }
      }
    }
  };

  private PlotPropertySheetPage _propertySheetPage;

  /**
   * constructor - quite simple really.
   */
  public PlotEditor()
  {
    super();

    // create the track manager to manage the primary & secondary tracks
    _trackDataProvider = new TrackManager(_myLayers);

    // and listen out form modifications, because we want to mark ourselves
    // as
    // dirty once they've updated
    _trackDataProvider.addTrackDataListener(new TrackDataListener()
    {
      public void tracksUpdated(final WatchableList primary,
          final WatchableList[] secondaries)
      {
        fireDirty();
      }
    });

    // sort out the time controlleroperations
    _timeControllerOperations =
        new org.mwc.cmap.core.interfaces.TimeControllerOperation.TimeControllerOperationStore();
    _timeControllerOperations.add(new ExportTimeDataToClipboard());
    _timeControllerOperations.add(new ExportToFlatFile());
    _timeControllerOperations.add(new ExportToFlatFile2());
    _timeControllerOperations.add(new ExportDopplerShift());

    _layerPainterManager = new LayerPainterManager(_trackDataProvider);
    _layerPainterManager.addPropertyChangeListener(new PropertyChangeListener()
    {
      public void propertyChange(final PropertyChangeEvent arg0)
      {
        // ok, trigger repaint of plot
        if (getChart() != null)
          getChart().update();
      }
    });

    // create the time manager. cool
    _timeManager = new TimeManager();
    _timeManager.addListener(_timeListener,
        TimeProvider.TIME_CHANGED_PROPERTY_NAME);

    // and how time is managed
    _timePreferences = new TimeControlProperties();

    // listen out for when our input changes, since we will change the
    // editor
    // window title
    this.addPropertyListener(new IPropertyListener()
    {

      @SuppressWarnings(
      {"synthetic-access"})
      public void propertyChanged(final Object source, final int propId)
      {
        if (propId == PROP_INPUT)
        {
          final Object input = getEditorInput();
          if (input instanceof IFileEditorInput)
          {
            final IFileEditorInput inp = (IFileEditorInput) getEditorInput();
            setPartName(inp.getName());
          }
          else if (input instanceof FileStoreEditorInput)
          {
            final FileStoreEditorInput fsi = (FileStoreEditorInput) input;
            final String theName = fsi.getName();
            setPartName(theName);
          }
          else if (input instanceof FileRevisionEditorInput)
          {
            setPartName(((FileRevisionEditorInput) input).getName());
          }
          else
          {
            CorePlugin.logError(Status.WARNING,
                "data source for PlotEditor not of expected type:" + input,
                null);
            setPartName(input.toString());
          }
        }
      }
    });

    _myOperations = new PlotOperations()
    {
      // just provide with our complete set of layers
      @SuppressWarnings("synthetic-access")
      public Object[] getTargets()
      {
        // ok, return our top level layers as objects
        final Vector<Layer> res = new Vector<Layer>(0, 1);
        for (int i = 0; i < _myLayers.size(); i++)
        {
          res.add(_myLayers.elementAt(i));
        }
        return res.toArray();
      }

      /**
       * override performing the operation, since we'll do a screen update on completion
       */
      @SuppressWarnings("synthetic-access")
      public Vector<Layer> performOperation(final AnOperation operationName)
      {
        // make the actual change
        final Vector<Layer> res = super.performOperation(operationName);

        if (res != null)
        {
          if (res.size() != 0)
          {
            for (final Iterator<Layer> iter = res.iterator(); iter.hasNext();)
            {
              final Layer thisL = (Layer) iter.next();
              // and update the screen
              _myLayers.fireReformatted(thisL);

            }
          }
        }

        return res;

      }
    };

    // do we have some time preferences?
    if (_timePreferences != null)
    {
      final HiResDate startDTG = _timePreferences.getSliderStartTime();
      final HiResDate endDTG = _timePreferences.getSliderEndTime();
      // and were there any times in it?
      if ((startDTG != null) && (endDTG != null))
      {
        // yup, store the time data.
        _myOperations
            .setPeriod(new TimePeriod.BaseTimePeriod(startDTG, endDTG));
      }
    }

    // introduce our new item listener that looks after relative TMA segments being dropped into the
    // layers object
    _myLayers.addDataExtendedListener(new Layers.DataListener2()
    {

      @Override
      public void dataModified(Layers theData, Layer changedLayer)
      {
      }

      @Override
      public void dataExtended(Layers theData)
      {
      }

      @Override
      public void dataReformatted(Layers theData, Layer changedLayer)
      {
      }

      @Override
      public void dataExtended(final Layers theData, final Plottable newItem,
          final HasEditables parent)
      {
        reconnectSegments(newItem, parent);
      }
    });
  }

  @Override
  public void dispose()
  {
    super.dispose();

    // stop listening to the time manager
    _timeManager.removeListener(_timeListener,
        TimeProvider.TIME_CHANGED_PROPERTY_NAME);

    _timeManager = null;

    if (_layerPainterManager != null)
    {
      _layerPainterManager.close();
      _layerPainterManager = null;
    }

    if (_outlinePage != null)
    {
      _outlinePage.dispose();
      _outlinePage = null;
    }
  }

  public void init(final IEditorSite site, final IEditorInput input)
      throws PartInitException
  {
    setSite(site);
    setInputWithNotify(input);

    // ok - declare and load the supplemental plugins which can load
    // datafiles
    initialiseFileLoaders();

    // and start the load
    loadThisFile(input);

    // lastly, set the title (if we have one)
    this.setPartName(input.getName());

    // hmm, does this input have an icon?
    ImageDescriptor icon = input.getImageDescriptor();
    if (icon != null)
    {
      this.setTitleImage(icon.createImage());
    }

    // ok, also sort out that refresh job
    createRefreshJob();
  }

  /**
   * @param input
   *          the file to insert
   */
  private void loadThisFile(IEditorInput input)
  {
    InputStream is = null;
    if (!input.exists())
    {
      CorePlugin.logError(Status.ERROR, "File cannot be found:"
          + input.getName(), null);
      return;
    }
    String name = input.getName();
    try
    {
      final IPersistableElement persist = input.getPersistable();
      if (input instanceof IFileEditorInput)
      {
        final IFileEditorInput ife = (IFileEditorInput) input;
        final IFile iff = ife.getFile();
        iff.refreshLocal(IResource.DEPTH_ONE, null);
        is = iff.getContents();
        name = getAbsoluteName(iff);
      }
      else if (persist instanceof IFileEditorInput)
      {
        final IFileEditorInput ifi = (IFileEditorInput) persist;
        IFile iff = ifi.getFile();
        is = iff.getContents();
        name = getAbsoluteName(iff);
      }
      else if (input instanceof FileStoreEditorInput)
      {
        final FileStoreEditorInput _input = (FileStoreEditorInput) input;
        final URI _uri = _input.getURI();
        final Path _p = new Path(_uri.getPath());
        name = _uri.getPath();
        final IFileStore _ifs = EFS.getLocalFileSystem().getStore(_p);
        is = _ifs.openInputStream(EFS.NONE, null);
      }
      else if (input instanceof FileRevisionEditorInput)
      {
        final FileRevisionEditorInput frei = (FileRevisionEditorInput) input;
        IFile file = getFile(frei);
        if (file != null && file.exists())
        {
          name = getAbsoluteName(file);
          is = frei.getStorage().getContents();
        }
      }
      if (is != null)
        loadThisStream(is, name);
      else
      {
        CorePlugin.logError(Status.INFO, "Failed to load file from:" + input,
            null);
      }

    }
    catch (final CoreException e)
    {
      CorePlugin.logError(Status.ERROR, "Resource out of sync:"
          + input.getName() + " REFRESH the workspace", null);
      MessageDialog
          .openError(
              Display.getDefault().getActiveShell(),
              "File out of sync",
              "This file has been edited or removed:"
                  + input.getName()
                  + "\nPlease right-click on your navigator project and press Refresh");
    }
  }

  private IFile getFile(FileRevisionEditorInput frei)
  {
    IFile file = null;
    IFileRevision revision = frei.getFileRevision();
    if (revision instanceof LocalFileRevision)
    {
      LocalFileRevision localFileRevision = (LocalFileRevision) revision;
      if (localFileRevision.getFile() != null)
      {
        file = localFileRevision.getFile();
      }
      else
      {
        IFileState state = localFileRevision.getState();
        IPath path = state.getFullPath();
        file = ResourcesPlugin.getWorkspace().getRoot().getFile(path);
      }
    }
    return file;
  }

  public String getAbsoluteName(final IFile iff) throws CoreException
  {
    String name;
    URI uri = iff.getLocationURI();
    if (iff.isLinked())
    {
      uri = iff.getRawLocationURI();
    }
    File javaFile = EFS.getStore(uri).toLocalFile(0, new NullProgressMonitor());
    name = javaFile.getAbsolutePath();
    return name;
  }

  /**
   * @param input
   *          the file to insert
   */
  private void loadThisFile(final String filePath)
  {
    FileInputStream ifs = null;
    try
    {
      ifs = new FileInputStream(filePath);
      loadThisStream(ifs, filePath);
    }
    catch (final FileNotFoundException e)
    {
      CorePlugin.logError(Status.ERROR,
          "Problem loading data file:" + filePath, e);
    }
    finally
    {
      if (ifs != null)
        try
        {
          ifs.close();
        }
        catch (IOException e)
        {
          CorePlugin.logError(Status.ERROR, "Problem closing input stream:"
              + filePath, e);
        }
    }
  }

  private void loadThisStream(final InputStream is, final String fileName)
  {
    // right, see if any of them will do our edit
    final IPlotLoader[] loaders = _loader.findLoadersFor(fileName);
    // did we find any?
    if (loaders.length > 0)
    {
      // cool, give them a go...
      try
      {
        for (int i = 0; i < loaders.length; i++)
        {
          final IPlotLoader thisLoader = loaders[i];

          // get it to load. Just in case it's an asychronous load
          // operation, we
          // rely on it calling us back (loadingComplete)
          thisLoader.loadFile(this, is, fileName);

          // special handling - popup a dialog to allow sensor name/color to be
          // set if there's just one sensor
          if (thisLoader instanceof DeferredPlotLoader)
          {
            final DeferredPlotLoader ld = (DeferredPlotLoader) thisLoader;
            final BaseLoader loader = ld.getLoader();
            if (loader != null)
            {
              if (loader instanceof ReplayLoader)
              {
                final ReplayLoader rl = (ReplayLoader) loader;
                final ImportReplay ir = rl.getReplayLoader();
                List<TrackWrapper> candidateHosts = null;

                final Vector<SensorWrapper> sensors = ir.getPendingSensors();

                // see if there are any sensors awaiting a host
                if (sensors.size() > 1)
                {
                  Iterator<SensorWrapper> sIter = sensors.iterator();
                  while (sIter.hasNext())
                  {
                    SensorWrapper sensor = (SensorWrapper) sIter.next();
                    if (sensor.getHost() == null)
                    {
                      // have we sorted out the hosts?
                      if (candidateHosts == null)
                      {
                        candidateHosts = determineCandidateHosts();
                      }

                      if (candidateHosts.size() == 0)
                      {
                        CorePlugin.showMessage("Loading sensor data",
                            "Sensor data can only be loaded after tracks");
                        return;
                      }

                      // ok, let the user choose
                      chooseHostFor(sensor, candidateHosts);
                    }
                  }
                }
                else if (sensors.size() == 1)
                {
                  // see if there are any sensors awaiting a color
                  final SensorWrapper thisS = sensors.firstElement();
                  final boolean success = nameThisSensor(thisS);

                  // does user wish to name/format sensor?
                  if (!success)
                  {
                    // nope, cancel the import.
                    ir.clearPendingSensorList();
                  }
                }

                // ok, now we can store the pending sensors in their
                // parent tracks
                ir.storePendingSensors();
              }
            }
          }
        }
      }
      catch (final RuntimeException e)
      {
        CorePlugin.logError(Status.ERROR, "Problem loading data file:"
            + fileName, e);
      }
    }
  }

  private List<TrackWrapper> determineCandidateHosts()
  {
    List<TrackWrapper> res = new ArrayList<TrackWrapper>();

    Enumeration<Editable> iter = _myLayers.elements();
    while (iter.hasMoreElements())
    {
      Editable editable = (Editable) iter.nextElement();
      if (editable instanceof TrackWrapper)
      {
        res.add((TrackWrapper) editable);
      }
    }

    return res;
  }

  private void chooseHostFor(SensorWrapper sensor,
      List<TrackWrapper> candidateHosts)
  {
    // ok, construct the popup
    Object[] tArr = candidateHosts.toArray();

    // popup the layers in a question dialog
    final IStructuredContentProvider theVals = new ArrayContentProvider();
    final ILabelProvider theLabels = new LabelProvider();

    // collate the dialog
    final ListDialog list =
        new ListDialog(Display.getCurrent().getActiveShell());
    list.setContentProvider(theVals);
    list.setLabelProvider(theLabels);
    list.setInput(tArr);
    list.setMessage("Please select the track for sensor titled \""
        + sensor.getName() + "\"\n(or Cancel to not store it)");
    list.setTitle("Sensor track not found");
    list.setHelpAvailable(false);

    // select the first item, so it's valid to press OK immediately
    list.setInitialSelections(new Object[]
    {tArr[0]});

    // open it
    final int selection = list.open();

    // did user say yes?
    if (selection != ListDialog.CANCEL)
    {
      // yup, store it's name
      final Object[] val = list.getResult();

      // check something got selected
      if (val.length == 1)
      {
        TrackWrapper selected = (TrackWrapper) val[0];
        sensor.setHost(selected);
      }
    }
  }

  private boolean nameThisSensor(final SensorWrapper thisS)
  {
    // right, just have a quick look and see if the sensor has range data -
    // because
    // if it doesn't we'll let the user set a default
    final boolean needsRange = areWeWaitingForRange(thisS);

    // next, just see if this track already contains sensor
    // data with this name
    final boolean alreadyLoaded = isThisSensorAlreadyPresent(thisS);

    // inform the user if this sensor name is already in use
    final String introString;
    if (alreadyLoaded)
    {
      introString =
          "a one-word title for this block of sensor contacts (e.g. S2046)\n\n"
              + "Note: [" + thisS.getName() + "] is already in use.";
    }
    else
    {
      introString =
          "a one-word title for this block of sensor contacts (e.g. S2046)";
    }

    SensorImportHelper importHelper =
        getSensorImportHelperFor(thisS.getName(), thisS.getColor(),
            introString, needsRange);

    // did it work?
    if (importHelper.success())
    {
      // ok, use the name
      thisS.setName(importHelper.getName());
      thisS.setColor(importHelper.getColor());
      thisS.setVisible(importHelper.getVisiblity());

      // are we doing range?
      if (needsRange)
      {
        final WorldDistance theRange = importHelper.getRange();

        // did a range get entered?
        if ((theRange != null) && (theRange.getValue() != 0))
        {
          final Enumeration<Editable> iter = thisS.elements();
          while (iter.hasMoreElements())
          {
            final SensorContactWrapper cut =
                (SensorContactWrapper) iter.nextElement();
            cut.setRange(new WorldDistance(theRange));
          }
        }
      }
      if (importHelper.applyRainbow())
      {
        applyRainbowShadingTo(thisS);
      }
    }

    return importHelper.success();
  }

  private boolean areWeWaitingForRange(final SensorWrapper thisS)
  {
    boolean needsRange = false;
    final Enumeration<Editable> cuts = thisS.elements();
    if (cuts.hasMoreElements())
    {
      final Editable firstCut = cuts.nextElement();
      final SensorContactWrapper scw = (SensorContactWrapper) firstCut;
      // do we have bearing?
      if (scw.getHasBearing())
      {
        // yes. now are we waiting for a range?
        if (scw.getRange() == null)
        {
          needsRange = true;
        }
      }
    }
    return needsRange;
  }

  private boolean isThisSensorAlreadyPresent(final SensorWrapper thisS)
  {
    boolean alreadyLoaded = false;
    final TrackWrapper trk = thisS.getHost();
    if (trk != null)
    {
      final Enumeration<Editable> enumer = trk.getSensors().elements();
      while (enumer.hasMoreElements())
      {
        final SensorWrapper oldS = (SensorWrapper) enumer.nextElement();
        if (oldS.getName().equals(thisS.getName()))
        {
          alreadyLoaded = true;
        }
      }
    }
    return alreadyLoaded;
  }

  private SensorImportHelper getSensorImportHelperFor(String sensorName,
      Color sensorColor, String introString, boolean needsRange)
  {
    // ok, check the property
    final String showImportWizard =
        CorePlugin.getToolParent().getProperty(
            PrefsPage.PreferenceConstants.USE_IMPORT_SENSOR_WIZARD);

    // create the relevant helper
    final SensorImportHelper helper;
    if (Boolean.parseBoolean(showImportWizard))
    {
      helper =
          new SensorImportHelper.SensorImportHelperUI(sensorName, sensorColor,
              introString, needsRange);
    }
    else
    {
      helper = new SensorImportHelper.SensorImportHelperHeadless(sensorName);
    }

    return helper;
  }

  private void applyRainbowShadingTo(final SensorWrapper thisS)
  {
    SensorWrapper theSensor = null;

    // are they items we're interested in?
    HiResDate startDTG = new HiResDate(Long.MAX_VALUE / 1000, 0);
    HiResDate endDTG = new HiResDate(0);
    Enumeration<Editable> elements = thisS.elements();
    ArrayList<Editable> sensors = new ArrayList<Editable>();
    ArrayList<SensorContactWrapper> list =
        new ArrayList<SensorContactWrapper>();
    while (elements.hasMoreElements())
    {
      sensors.add(elements.nextElement());
    }
    for (Editable thisE : sensors)
    {
      if (thisE instanceof SensorWrapper)
      {
        // just check that there's only one item selected
        if (sensors.size() == 1)
        {
          theSensor = (SensorWrapper) thisE;
        }
      }
      else if (thisE instanceof SensorContactWrapper)
      {
        list.add((SensorContactWrapper) thisE);
        if (startDTG.compareTo(((SensorContactWrapper) thisE).getDTG()) > 0)
        {
          startDTG = ((SensorContactWrapper) thisE).getDTG();
        }
        if (endDTG.compareTo(((SensorContactWrapper) thisE).getDTG()) < 0)
        {
          endDTG = ((SensorContactWrapper) thisE).getDTG();
        }
      }
    }

    // ok, do we have a single sensor?
    if (theSensor != null)
    {
      startDTG = theSensor.getStartDTG();
      endDTG = theSensor.getEndDTG();
      Collection<Editable> editables =
          theSensor.getItemsBetween(theSensor.getStartDTG(), theSensor
              .getEndDTG());
      for (Editable editable : editables)
      {
        if (editable instanceof SensorContactWrapper)
        {
          list.add((SensorContactWrapper) editable);
        }
      }
    }
    final HiResDate start = startDTG;
    final HiResDate end = endDTG;
    // create this operation
    final String title1 = "Shade in rainbow colors";
    Layer parentLayer = null;

    Layers parentLayers = _myLayers;
    if (parentLayers != null)
    {
      if (parentLayers.size() == 1)
      {
        parentLayer = parentLayers.elementAt(0);
      }
    }

    final IUndoableOperation theAction =
        new ShadeCutsOperation(title1, parentLayers, parentLayer, list
            .toArray(new SensorContactWrapper[0]), start, end,
            ShadeOperation.RAINBOW_SHADE);
    CorePlugin.run(theAction);
  }

  /**
	 * 
	 */
  private void initialiseFileLoaders()
  {
    // hey - sort out our plot readers
    _loader = new LoaderManager(EXTENSION_POINT_ID, EXTENSION_TAG, PLUGIN_ID)
    {

      public INamedItem createInstance(
          final IConfigurationElement configElement, final String label)
      {
        // get the attributes
        final String theLabel =
            configElement.getAttribute(EXTENSION_TAG_LABEL_ATTRIB);
        final String icon =
            configElement.getAttribute(EXTENSION_TAG_ICON_ATTRIB);
        final String fileTypes =
            configElement.getAttribute(EXTENSION_TAG_EXTENSIONS_ATTRIB);
        final String firstLine =
            configElement.getAttribute(EXTENSION_TAG_FIRST_LINE_ATTRIB);

        // create the instance
        final INamedItem res =
            new IPlotLoader.DeferredPlotLoader(configElement, theLabel, icon,
                fileTypes, firstLine);

        // and return it.
        return res;
      }

    };
  }

  private static TimePeriod getPeriodFor(final Layers theData)
  {
    TimePeriod res = null;

    for (final Enumeration<Editable> iter = theData.elements(); iter
        .hasMoreElements();)
    {
      final Layer thisLayer = (Layer) iter.nextElement();

      // and through this layer
      if (thisLayer instanceof TrackWrapper)
      {
        final TrackWrapper thisT = (TrackWrapper) thisLayer;
        res = extend(res, thisT.getStartDTG());
        res = extend(res, thisT.getEndDTG());
      }
      else if (thisLayer instanceof BaseLayer)
      {
        final Enumeration<Editable> elements = thisLayer.elements();
        while (elements.hasMoreElements())
        {
          final Plottable nextP = (Plottable) elements.nextElement();
          if (nextP instanceof Watchable)
          {
            final Watchable wrapped = (Watchable) nextP;
            final HiResDate dtg = wrapped.getTime();
            if (dtg != null)
            {
              res = extend(res, dtg);

              // also see if it this data type an end time
              if (wrapped instanceof WatchableList)
              {
                // ok, make sure we also handle the end time
                final WatchableList wl = (WatchableList) wrapped;
                final HiResDate endD = wl.getEndDTG();
                if (endD != null)
                  res = extend(res, endD);
              }
            }
          }
        }
      }
    }

    return res;
  }

  private static TimePeriod
      extend(final TimePeriod period, final HiResDate date)
  {
    TimePeriod result = period;
    // have we received a date?
    if (date != null)
    {
      if (result == null)
      {
        result = new TimePeriod.BaseTimePeriod(date, date);
      }
      else
        result.extend(date);
    }

    return result;
  }

  /**
   * method called when a helper object has completed a plot-load operation
   * 
   * @param source
   */
  public void loadingComplete(final Object source)
  {

    // ok, stop listening for dirty calls - since there will be so many and
    // we
    // don't want
    // to start off with a dirty plot
    startIgnoringDirtyCalls();

    DebriefPlugin.logError(Status.INFO, "File loading complete received", null);

    // and update the time management bits
    final TimePeriod timePeriod = getPeriodFor(_myLayers);

    if (timePeriod != null)
    {
      _timeManager.setPeriod(this, timePeriod);

      // also give it a current DTG (if it doesn't have one)
      if (_timeManager.getTime() == null)
        _timeManager.setTime(this, timePeriod.getStartDTG(), false);
    }

    // done - now we can process dirty calls again
    stopIgnoringDirtyCalls();

  }

  protected void filesDropped(final String[] fileNames)
  {
    super.filesDropped(fileNames);

    // ok, iterate through the files
    for (int i = 0; i < fileNames.length; i++)
    {
      final String thisFilename = fileNames[i];
      loadThisFile(thisFilename);
    }

    // ok, we're probably done - fire the update
    this._myLayers.fireExtended();

    // and resize to make sure we're showing all the data
    this._myChart.rescale();

    // hmm, we may have loaded more track data - but we don't track
    // loading of individual tracks - just fire a "modified" flag
    _trackDataProvider.fireTracksChanged();

  }

  private static boolean _updatingPlot = false;

  @Override
  public void selectPlottable(Plottable tgt, Layer parentLayer)
  {
    // handle some special cases.
    if (tgt instanceof FixWrapper)
    {
      CorePlugin.logError(Status.INFO,
          "Double-click processed, fixing parent levels for:" + tgt, null);

      // ok, we have to generate the correct object model
      FixWrapper fix = (FixWrapper) tgt;
      TrackSegment segment = fix.getSegment();
      TrackWrapper track = segment.getWrapper();
      SegmentList segList = track.getSegments();
      Layers layers = getChart().getLayers();
      final ISelection selected =
          wrapObjects(parentLayer, fix, segment, segList, layers);
      fireSelectionChanged(selected);
    }
    else if (tgt instanceof SensorContactWrapper)
    {
      CorePlugin.logError(Status.INFO,
          "Double-click processed, fixing parent levels for:" + tgt, null);
      
      // ok, we have to generate the correct object model
      SensorContactWrapper cut = (SensorContactWrapper) tgt;
      SensorWrapper sensor = cut.getSensor();
      TrackWrapper track = sensor.getHost();
      BaseLayer sList = track.getSensors();
      Layers layers = getChart().getLayers();
      final ISelection selected =
          wrapObjects(parentLayer, cut, sensor, sList, layers);
      fireSelectionChanged(selected);
    }
    else if (tgt instanceof TMAContactWrapper)
    {
      CorePlugin.logError(Status.INFO,
          "Double-click processed, fixing parent levels for:" + tgt, null);
      
      // ok, we have to generate the correct object model
      TMAContactWrapper cut = (TMAContactWrapper) tgt;
      TMAWrapper sensor = cut.getTMATrack();
      TrackWrapper track = sensor.getHost();
      BaseLayer sList = track.getSolutions();
      Layers layers = getChart().getLayers();
      final ISelection selected =
          wrapObjects(parentLayer, cut, sensor, sList, layers);
      fireSelectionChanged(selected);
    }
    else
    {
      super.selectPlottable(tgt, parentLayer);
    }
  }

  private ISelection wrapObjects(Layer track, Editable item,
      Editable itemParent, Editable parentList, Layers layers)
  {
    final EditableWrapper parentP =
        new EditableWrapper(track, null, layers);
    final EditableWrapper segListW = new EditableWrapper(parentList, parentP, layers);
    final EditableWrapper segmentW = new EditableWrapper(itemParent, segListW, layers);
    final EditableWrapper fixW = new EditableWrapper(item, segmentW, layers);
    final ISelection selected = new StructuredSelection(fixW);
    return selected;
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.mwc.cmap.plotViewer.editors.CorePlotEditor#timeChanged()
   */
  protected void timeChanged(final HiResDate newDTG)
  {
    super.timeChanged(newDTG);

    // just check we're ready for plotting.
    if (getChart() == null)
      return;

    if (_updatingPlot)
    {
      // skip the update - we're already at it
    }
    else
    {
      _updatingPlot = true;

      try
      {
        // note, we've learn't to use the default display instead of the
        // current one, we were get a null returned since this thread may not
{'fraction_non_alphanumeric': 0.09327048440477384, 'fraction_numerical': 0.0014040718082439074, 'mean_word_length': 2.527884657909867, 'pattern_counts': {'":': 0, '<': 61, '<?xml version=': 0, '>': 59, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 6}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6648413', 'n_tokens_mistral': 23105, 'n_tokens_neox': 22468, 'n_words': 5409}