/*
 * Licensed Materials - Property of Perforce Software, Inc. 
 * © Copyright Perforce Software, Inc. 2014, 2021 
 * © Copyright IBM Corp. 2009, 2014
 * © Copyright ILOG 1996, 2009
 * All Rights Reserved.
 *
 * Note to U.S. Government Users Restricted Rights:
 * The Software and Documentation were developed at private expense and
 * are "Commercial Items" as that term is defined at 48 CFR 2.101,
 * consisting of "Commercial Computer Software" and
 * "Commercial Computer Software Documentation", as such terms are
 * used in 48 CFR 12.212 or 48 CFR 227.7202-1 through 227.7202-4,
 * as applicable.
 */

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Enumeration;

import javax.swing.AbstractButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JRootPane;
import javax.swing.JSplitPane;
import javax.swing.SwingUtilities;
import javax.swing.plaf.basic.BasicHTML;
import javax.swing.tree.DefaultMutableTreeNode;

import ilog.views.IlvGraphic;
import ilog.views.IlvGraphicBag;
import ilog.views.IlvManager;
import ilog.views.IlvManagerLayer;
import ilog.views.IlvManagerView;
import ilog.views.IlvPoint;
import ilog.views.IlvRect;
import ilog.views.IlvTransformer;
import ilog.views.graphic.IlvEllipse;
import ilog.views.interactor.IlvMakeRectangleInteractor;
import ilog.views.interactor.IlvRectangularObjectFactory;
import ilog.views.io.IlvFieldNotFoundException;
import ilog.views.io.IlvInputStream;
import ilog.views.io.IlvOutputStream;
import ilog.views.io.IlvReadFileException;
import ilog.views.maps.IlvAreasOfInterestProperty;
import ilog.views.maps.IlvAttributeProperty;
import ilog.views.maps.IlvCoordinate;
import ilog.views.maps.IlvCoordinateSystemProperty;
import ilog.views.maps.IlvFeatureAttribute;
import ilog.views.maps.IlvFeatureAttributeProperty;
import ilog.views.maps.IlvMapGeometry;
import ilog.views.maps.IlvMapInputStream;
import ilog.views.maps.IlvMapLayerTreeProperty;
import ilog.views.maps.IlvMapOutputStream;
import ilog.views.maps.IlvMapScaleLimiter;
import ilog.views.maps.IlvMapUtil;
import ilog.views.maps.attribute.IlvStringAttribute;
import ilog.views.maps.beans.IlvJCoordinateSystemEditorPanel;
import ilog.views.maps.beans.IlvJMapsManagerViewControlBar;
import ilog.views.maps.beans.IlvLayerTreePanel;
import ilog.views.maps.beans.IlvMapAnnotationModel;
import ilog.views.maps.beans.IlvMapAnnotationProperty;
import ilog.views.maps.beans.IlvMapAnnotationToolBar;
import ilog.views.maps.beans.IlvMapLayer;
import ilog.views.maps.beans.IlvMapLayerTreeModel;
import ilog.views.maps.datasource.IlvGraphicLayerDataSource;
import ilog.views.maps.datasource.IlvMapDataSource;
import ilog.views.maps.datasource.IlvMapDataSourceModel;
import ilog.views.maps.datasource.IlvMapDataSourceProperty;
import ilog.views.maps.datasource.IlvShapeDataSource;
import ilog.views.maps.format.image.IlvRasterBasicImageReader;
import ilog.views.maps.geometry.IlvMapEllipse;
import ilog.views.maps.graphic.IlvMapGraphic;
import ilog.views.maps.graphic.IlvMapSelectionFactory;
import ilog.views.maps.graphic.style.IlvGraphicPathStyle;
import ilog.views.maps.graphic.style.IlvMapStyle;
import ilog.views.maps.graphic.style.IlvMapStyleBeanInfo;
import ilog.views.maps.graphic.style.IlvPolylineStyle;
import ilog.views.maps.label.IlvMapLabeler;
import ilog.views.maps.label.IlvMapLabelerProperty;
import ilog.views.maps.projection.IlvProjectionDictionary;
import ilog.views.maps.raster.IlvRasterAbstractReader;

import ilog.views.maps.raster.datasource.IlvRasterDataSourceFactory;
import ilog.views.maps.srs.coordsys.IlvCoordinateSystem;
import ilog.views.maps.srs.coordsys.IlvGeographicCoordinateSystem;
import ilog.views.maps.srs.coordtrans.IlvCoordinateTransformation;
import ilog.views.maps.srs.coordtrans.IlvCoordinateTransformationException;
import ilog.views.maps.theme.IlvMapStyleControllerProperty;
import ilog.views.swing.IlvJScrollManagerView;
import ilog.views.swing.IlvToolTipManager;
import ilog.views.tiling.IlvThreadedTileLoader;
import ilog.views.tiling.IlvTileLoader;
import ilog.views.tiling.IlvTiledLayer;
import ilog.views.util.IlvProductUtil;


/**
 * Draw on map demo. The purpose of this demonstration is to show how to put
 * graphics on top of a map using the IlvGraphicLayerDataSource.
 */
SuppressWarnings("serial")
public class DrawOnMap extends JRootPane {

  static {
   // JV-4073
    // increase number of projected points to find the total image size
    IlvRasterAbstractReader.setDefaultMaximumIntervalsForDeterminingImageLocation(100);
  }

  {
    // This sample uses JViews Maps features. When deploying an
    // application that includes this code, you need to be in possession
    // of a Perforce JViews Maps Deployment license.
    IlvProductUtil.DeploymentLicenseRequired(IlvProductUtil.JViews_Maps_Deployment);
  }

  private static final String WORLD_JPG = "world.jpg";//$NON-NLS-1$
  private static final String WORLD_SHP = "World.shp";//$NON-NLS-1$

  // last saved files
  private File ivlFile;

  // main map view
  private IlvManager manager = new IlvManager(0);
  private IlvManagerView view = new IlvManagerView(manager) {
    Override
    public void fitTransformerToContent(Insets i) {
      IlvManager pmanager = getManager();
      IlvMapDataSourceProperty p = (IlvMapDataSourceProperty) pmanager.getNamedProperty(IlvMapDataSourceProperty.NAME);
      Enumeration<?> e = p.getMapDataSourceModel().getEnumeration();
      while (e.hasMoreElements()) {
        DefaultMutableTreeNode node = (DefaultMutableTreeNode) e.nextElement();
        Object o = node.getUserObject();
        if (!(o instanceof IlvMapDataSource))
          continue;
        IlvMapDataSource source = (IlvMapDataSource) o;
        if (!WORLD_SHP.equals(source.getName()))
          pmanager.setVisible(source.getInsertionLayer().getManagerLayer().getIndex(), false, false);
      }

      super.fitTransformerToContent(i);

      e = p.getMapDataSourceModel().getEnumeration();
      while (e.hasMoreElements()) {
        DefaultMutableTreeNode node = (DefaultMutableTreeNode) e.nextElement();
        Object o = node.getUserObject();
        if (!(o instanceof IlvMapDataSource))
          continue;
        IlvMapDataSource source = (IlvMapDataSource) o;
        if (!WORLD_SHP.equals(source.getName()))
          pmanager.setVisible(source.getInsertionLayer().getManagerLayer().getIndex(), true, false);
      }

    }

  };

  /**
   * Return a HTML formated text when argument contains new lines.
   * 
   * @param s
   *          The string
   * @return The HTML formated string.
   */
  String getFormatedExtendedText(String s) {
    if (BasicHTML.isHTMLString(s))
      return s;
    String sp[] = s.split("\n"); //$NON-NLS-1$
    if (sp.length <= 1)
      return s;
    s = "<html> "; //$NON-NLS-1$
    for (int i = 0; i < sp.length; i++) {
      if (i > 0)
        s += " <br> "; //$NON-NLS-1$
      s += "\n" + sp[i]; //$NON-NLS-1$
    }
    s += "\n </html> "; //$NON-NLS-1$
    return s;
  }

  // coordinate system panel
  private IlvJCoordinateSystemEditorPanel coordPanel;
  // scroll around the map view
  private IlvJScrollManagerView viewScroll = new IlvJScrollManagerView(view) {
    Override
    public String getToolTipText(MouseEvent event) {
      Point pt = SwingUtilities.convertPoint(this, event.getPoint(), view);
      // Find component located at point pt
      IlvGraphic o = view.getManager().getObject(new IlvPoint(pt.x, pt.y), view);
      if (o != null) {
        IlvFeatureAttributeProperty p = (IlvFeatureAttributeProperty) o.getNamedProperty(IlvAttributeProperty.NAME);
        if (p != null) {
          IlvStringAttribute att = null;
          try {
            att = (IlvStringAttribute) p.getValue(IlvMapAnnotationToolBar.EXT_ANNOTATION_PROPERTY_NAME);
          } catch (IllegalArgumentException e) {
            att = null;
          }
          if (att != null) {
            String v = att.getString();
            if (v != null && !"".equals(v) && !"__null__".equals(v)) //$NON-NLS-1$//$NON-NLS-2$
              return getFormatedExtendedText(v);
          }
        }
      }
      return null;
    }
  };

  // zoom & selection toolbar
  private IlvJMapsManagerViewControlBar viewToolbar = new IlvJMapsManagerViewControlBar();
  private IlvMapAnnotationToolBar annotationsToolBar = new IlvMapAnnotationToolBar();
  // The save button
  private IlvMapAnnotationToolBar.ToolBarButton save;
  // The load button
  private IlvMapAnnotationToolBar.ToolBarButton load;

  private PropertyChangeListener coordinateSystemChanged = new PropertyChangeListener() {
    Override
    public void propertyChange(PropertyChangeEvent evt) {
      IlvCoordinateSystem system = (IlvCoordinateSystem) evt.getNewValue();
      // change coordinate system of the map according to panel's selection
      manager.setNamedProperty(new IlvCoordinateSystemProperty(system));
      // erase existing data in manager, and reload sources.

      clearManager(false);

      IlvMapDataSourceModel dsm = IlvMapDataSourceProperty.GetMapDataSourceModel(manager);
      DefaultMutableTreeNode root = (DefaultMutableTreeNode) dsm.getRoot();
      int count = root.getChildCount();
      for (int i = 0; i < count; i++) {
        DefaultMutableTreeNode node = (DefaultMutableTreeNode) root.getChildAt(i);
        IlvMapDataSource source = (IlvMapDataSource) node.getUserObject();
        IlvMapLayer layer = source.getInsertionLayer();
        if (layer.getManager() == manager) {
          source.reset();
          try {
            source.start();
          } catch (Exception e) {
            e.printStackTrace();
          }
          IlvManagerLayer layers[] = layer.getManagerLayers();
          for (int l = 0; l < layers.length; l++) {
            if (layers[l] instanceof IlvTiledLayer) {
              ((IlvTiledLayer) layers[l]).getTileController().updateView(view);
            }
          }
        }
      }
      view.fitTransformerToContent();
    }

  };

  /**
   * Constructor .
   */
  public DrawOnMap() {
    super();
    manager.setSelectionFactory(new IlvMapSelectionFactory());
    // Show all the style properties
    IlvMapStyleBeanInfo.setAdvancedMode(true);
    // Make sure the swing construction is called in Swing event thread.
    ilog.views.util.swing.IlvSwingUtil.invokeAndWait(new Runnable() {
      Override
      public void run() {
        // Toolbar feed
        setupToolbar();
        // configure view
        setupView();
        // configure GUI
        setupGUI();
        // Load the initial file
        loadData(true);
        // JV-2760
        IlvToolTipManager.registerView(view);
      }
    }); // event thread runnable
  }


  IlvJCoordinateSystemEditorPanel getCoordPanel() {
    if (coordPanel == null) {
      coordPanel = new IlvJCoordinateSystemEditorPanel() {
        Override
        public IlvProjectionDictionary createProjectionDictionary() {
          return new ProjectionDictionary();
        }
      };
    }
    return coordPanel;
  }

  /**
   * build GUI.
   */
  void setupGUI() {
    IlvJCoordinateSystemEditorPanel cp = getCoordPanel();
    cp.setAdvancedPanelsVisible(false);
    IlvCoordinateSystem cs = IlvCoordinateSystemProperty.GetCoordinateSystem(manager);
    cp.setCoordinateSystem(cs);
    cp.addCoordinateSystemChangeListener(coordinateSystemChanged);
    IlvLayerTreePanel layerTree = new IlvLayerTreePanel();
    layerTree.setView(view);

    JSplitPane split1 = new JSplitPane();
    split1.setOrientation(JSplitPane.VERTICAL_SPLIT);
    split1.setLeftComponent(cp);
    split1.setRightComponent(layerTree);

    JSplitPane split2 = new JSplitPane();
    split2.setLeftComponent(split1);
    split2.setRightComponent(viewScroll);
    getContentPane().add(split2, BorderLayout.CENTER);
    JPanel toolBarPanel = new JPanel(new BorderLayout());
    toolBarPanel.add(viewToolbar, BorderLayout.LINE_START);
    toolBarPanel.add(annotationsToolBar);
    getContentPane().add(toolBarPanel, BorderLayout.NORTH);
  }

  /**
   * configure view.
   */
  void setupView() {
    view.setSize(new Dimension(800, 600));
    view.setKeepingAspectRatio(true);
    viewScroll.setPreferredSize(view.getSize());
    // geo reference the manager

    manager.setNamedProperty(new IlvCoordinateSystemProperty(getCoordPanel().getCoordinateSystem()));

    // limit the zoom to correct scales.
    IlvMapScaleLimiter limiter = new IlvMapScaleLimiter(1f / 10000000f, (float) (1 / 1E9));
    limiter.setView(view);
    view.setAntialiasing(true);
  }

  /**
   * configure toolbar
   */
  void setupToolbar() {
    viewToolbar.setView(view);
    annotationsToolBar.setView(view);

    // save button.
    ActionListener sac = new ActionListener() {
      Override
      public void actionPerformed(ActionEvent e) {
        ((AbstractButton) e.getSource()).setSelected(false);
        try {
          // create temp files fore writting the manager.
          if (ivlFile != null)
            ivlFile.delete();
          ivlFile = File.createTempFile("DrawOnMap", IlvMapOutputStream.getFileSuffix()); //$NON-NLS-1$
          ivlFile.deleteOnExit();
          FileOutputStream stream = new FileOutputStream(ivlFile);
          message("saving to " + ivlFile.getAbsolutePath());//$NON-NLS-1$ //
                                                            // should
                                                            // betranslated
          IlvMapOutputStream str = new IlvMapOutputStream(stream, null, false);
          // write only data sources, not the objects
          str.setWritingObjects(false);
          // write the content of the manager, that is data sources, layer and
          // data source tree.
          str.write(manager);
          str.flush();
          str.closeImageStream();
          stream.close();
          message("saving done");// should be translated //$NON-NLS-1$
          load.setEnabled(true);
        } catch (IOException e1) {
          e1.printStackTrace();
        }
      }
    };
    save = annotationsToolBar.new ToolBarButton("/save.gif", sac, "Save to Temporary File");//$NON-NLS-1$ // //$NON-NLS-2$
                                                                                            // should
                                                                                            // be
                                                                                            // translated

    // load button
    ActionListener lac = new ActionListener() {
      Override
      public void actionPerformed(ActionEvent e) {
        ((AbstractButton) e.getSource()).setSelected(false);
        try {
          InputStream str = null;
          if (ivlFile != null)
            str = new FileInputStream(ivlFile);
          if (str != null) {
            message("loading from " + ivlFile.getAbsolutePath());//$NON-NLS-1$ //
                                                                 // should be
                                                                 // translated
            clearManager(true);
            IlvMapInputStream stream = new IlvMapInputStream(str, (String) null);
            stream.read(manager);
            str.close();
            view.fitTransformerToContent();
          }
        } catch (IOException e1) {
          e1.printStackTrace();
        } catch (IlvReadFileException e1) {
          e1.printStackTrace();
        }
      }
    };
    load = annotationsToolBar.new ToolBarButton("/load.gif", lac, "Load from Temporary File");//$NON-NLS-1$ // //$NON-NLS-2$
                                                                                              // should
                                                                                              // be
                                                                                              // translated
    load.setEnabled(false);


    if (save != null)
      viewToolbar.add(save);
    if (load != null)
      viewToolbar.add(load);

    IlvMapAnnotationToolBar.ToolBarButton b;

    b = annotationsToolBar.new ToolBarButton("/ellipse.gif", new MakeEllipseInteractor(), //$NON-NLS-1$
        "Make an Ellipse Annotation");// //$NON-NLS-1$
                                      // should
                                      // be
                                      // translated

    annotationsToolBar.add(b);

  }

  /**
   * Creates a map. All map creation steps are performed here.
   */
  private void loadData(boolean start) {

    URL jpgURL = DrawOnMap.class.getResource(WORLD_JPG);
    URL shpFile = DrawOnMap.class.getResource(WORLD_SHP);

    try {
      IlvShapeDataSource shpDataSource = new IlvShapeDataSource(shpFile, false);
      shpDataSource.setName(WORLD_SHP);
      shpDataSource.setCoordinateSystem(IlvGeographicCoordinateSystem.WGS84);
      // create a raster reader for the gif file (necessary to create a
      // datasource below)
      IlvRasterBasicImageReader imageReader = new IlvRasterBasicImageReader();
      imageReader.addMap(jpgURL);
      // georeference this image (it covers the whole earth)
      imageReader.setImageBounds(0, -Math.PI, Math.PI / 2, Math.PI, -Math.PI / 2);

      // create a datasource for the jpg file.
      IlvMapDataSource imageDataSource = IlvRasterDataSourceFactory.buildTiledImageDataSource(manager, imageReader,
          true, true, null);
      imageDataSource.setName(WORLD_JPG);
      // insert it in the manager's data source tree
      IlvMapDataSourceModel dataSourceModel = IlvMapDataSourceProperty.GetMapDataSourceModel(manager);

      dataSourceModel.insert(shpDataSource);
      dataSourceModel.insert(imageDataSource);

      IlvMapLayer shpLayer = shpDataSource.getInsertionLayer();
      shpLayer.setName("ESRI layer (world.shp)"); //$NON-NLS-1$ // should be
                                                  // translated
      // setup the shape layer style
      shpLayer.setStyle(new IlvGraphicPathStyle());
      shpLayer.getStyle().setAttribute(IlvPolylineStyle.FOREGROUND, Color.cyan.darker());
      shpLayer.getStyle().setAttribute(IlvGraphicPathStyle.PAINT, new Color(1, 1, 1, 0.25f));

      IlvMapLayer imageLayer = imageDataSource.getInsertionLayer();
      imageLayer.setName("Image layer (world.jpg)"); //$NON-NLS-1$ // should be
                                                     // translated
      // insert it on the manager's map layer tree
      IlvMapLayerTreeModel ltm = IlvMapLayerTreeProperty.GetMapLayerTreeModel(manager);
      ltm.addChild(null, imageLayer);
      ltm.addChild(null, shpLayer);

      IlvMapLayerTreeProperty.GetMapLayerTreeModel(manager).arrangeLayers();
      // start reading (recusively start all data sources of this model)
      if (start)
        dataSourceModel.start();
    } catch (Exception e1) {
      e1.printStackTrace();
    }
    view.setTripleBufferedLayerCount(view.getManager().getLayersCount());
    // fit the view
    view.fitTransformerToContent();
    view.repaint();
  }

  /**
   * Removes all layers from a manager and clear the associated model
   */
  private void clearManager(boolean removeProperties) {
    if (removeProperties) {// JV-2853
      IlvMapLayerTreeModel model = IlvMapLayerTreeProperty.GetMapLayerTreeModel(manager);
      model.clearAllObjects();
    }
    manager.deleteAll(false);
    if (removeProperties)
      while (manager.getLayersCount() > 0) {
        if (true) {
          IlvManagerLayer layer = manager.getManagerLayer(0);
          if (layer instanceof IlvTiledLayer) {
            IlvTileLoader loader = ((IlvTiledLayer) layer).getTileLoader();
            if (loader instanceof IlvThreadedTileLoader) {
              ((IlvThreadedTileLoader) loader).dispose();
            }
            ((IlvTiledLayer) layer).getTileController().removeAllFreeTiles();
          }
        }
        manager.removeLayer(0, false);
      }

    if (removeProperties) {
      manager.removeNamedProperty(IlvMapLayerTreeProperty.NAME);
      manager.removeNamedProperty(IlvMapDataSourceProperty.NAME);
      manager.removeNamedProperty(IlvAreasOfInterestProperty.NAME);
      manager.removeNamedProperty(IlvMapStyleControllerProperty.NAME);
      manager.removeNamedProperty(IlvMapLabelerProperty.NAME);
      manager.removeNamedProperty(IlvMapAnnotationProperty.NAME);
    }
  }

  void message(String s) {
    System.out.println(s);
  }

  /**
   * @param a
   */
  public static void main(String a[]) {
    // Sun recommends that to put the entire GUI initialization into the
    // AWT thread
    javax.swing.SwingUtilities.invokeLater(new Runnable() {
      Override
      public void run() {
        DrawOnMap demo = new DrawOnMap();
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(demo);
        frame.pack();
        frame.setTitle("Draw On Map Demo"); //$NON-NLS-1$
        frame.setVisible(true);
      }
    });
  }

  class ProjectionDictionary extends IlvProjectionDictionary {
    ProjectionDictionary() {
      super();
      unregisterProjection("Polyconic"); //$NON-NLS-1$
    }
  }

  /**
   * Interactor to create an ellipse interaction.
   */
  public class MakeEllipseInteractor extends IlvMakeRectangleInteractor {
    /**
     * Object name
     */
    protected static final String ELLIPSE = "Ellipse"; //$NON-NLS-1$ // should
                                                       // be translated

    IlvRectangularObjectFactory of = new IlvRectangularObjectFactory() {
      Override
      public IlvGraphic createObject(IlvRect rect) {
        IlvManager pmanager = annotationsToolBar.getView().getManager();
        if (rect.width < 3 && rect.height < 3)
          rect.expand(2f);
        setFillOn(true);
        MapEllipse ellipse = new MapEllipse(rect, true, true);

        IlvMapAnnotationModel model = IlvMapAnnotationProperty.GetMapAnnotationModel(pmanager);
        IlvGraphicLayerDataSource dataSource = model.getDataSource(pmanager, ELLIPSE);
        String name = ELLIPSE + " Annotation"; //$NON-NLS-1$ // should be
                                               // translated
        dataSource.getInsertionLayer().setName(name);
        dataSource.add(ellipse, IlvCoordinateSystemProperty.GetCoordinateSystem(pmanager));
        if (dataSource.getInsertionLayer().getStyle() == null) {
          MapEllipseStyle style = new MapEllipseStyle();
          style.setFill(true);
          style.setAttributeInfo(IlvMapAnnotationModel.info);
          style.setLabelAttribute(IlvMapAnnotationModel.info.getAttributeName(0));
          dataSource.getInsertionLayer().setStyle(style);
        }
        dataSource.getInsertionLayer().setAllowingMoveObjects(true);
        ellipse.setStyle(dataSource.getInsertionLayer().getStyle());
        pmanager.setInsertionLayer(dataSource.getInsertionLayer().getManagerLayer().getIndex());
        String[] s = annotationsToolBar.askForStrings(null, null);
        IlvFeatureAttributeProperty properties = new IlvFeatureAttributeProperty(IlvMapAnnotationModel.info,
            new IlvFeatureAttribute[] {
                s == null ? new IlvStringAttribute((String) null) : new IlvStringAttribute(s[0]),
                s == null ? new IlvStringAttribute((String) null) : new IlvStringAttribute(s[1]) });
        ellipse.setNamedProperty(properties);
        ellipse.setPopupMenu(annotationsToolBar.getPopupMenu());
        IlvMapSelectionFactory.setEditable(ellipse, true);
        if (s != null) {
          IlvMapLabeler labeler = IlvMapLabelerProperty.GetMapLabeler(pmanager);
          labeler.setView(getManagerView());
          labeler.addLayer(dataSource.getInsertionLayer());
          labeler.performLabeling();
        }
        return ellipse;
      }
    };

    /**
     * Creates a new <code>MakeEllipseInteractor</code>.
     */
    public MakeEllipseInteractor() {
      super();
      setObjectFactory(of);
    }

    Override
    protected void drawGhost(Graphics g) {
      IlvRect r = getDraggedRectangle();
      g.drawArc(Math.round((float) r.x), Math.round((float) r.y), Math.round((float) r.width), Math.round((float) r.height), 0, 360);
    }

  }

  /**
   * Ellipse map object
   */
  public static class MapEllipse extends IlvEllipse implements IlvMapGraphic {
    private static final String STYLE_FIELD = "style"; //$NON-NLS-1$
    /**
     * ellipse style
     */
    protected MapEllipseStyle style;

    /**
     * Creates a new <code>MapEllipse</code>.
     * 
     * @param r
     * @param b
     * @param c
     */
    public MapEllipse(IlvRect r, boolean b, boolean c) {
      super(r, b, c);
      IlvMapSelectionFactory.setEditable(this, true);
    }

    /**
     * Creates a new <code>MapEllipse</code>.
     * 
     * @param source
     */
    public MapEllipse(MapEllipse source) {
      super(source);
      IlvMapSelectionFactory.setEditable(this, true);
    }

    Override
    public IlvGraphic copy() {
      return new MapEllipse(this);
    }

    Override
    public void write(IlvOutputStream stream) throws IOException {
      super.write(stream);
      if (style != null)
        stream.write(STYLE_FIELD, style);
    }

    /**
     * Creates a new <code>MapEllipse</code>.
     * 
     * @param stream
     * @throws IlvReadFileException
     */
    public MapEllipse(IlvInputStream stream) throws IlvReadFileException {
      super(stream);
      try {
        setStyle((IlvMapStyle) stream.readPersistentObject(STYLE_FIELD));
      } catch (IlvFieldNotFoundException e) {
        // ignore
      }
    }

    /**
     * Applies the transformation. Labeling is performed if necessary.
     * 
     * @see ilog.views.graphic.IlvMarker#applyTransform(ilog.views.IlvTransformer)
     */
    Override
    public void applyTransform(IlvTransformer t) {
      super.applyTransform(t);
      IlvGraphicBag bag = getTopLevelGraphicBag();
      if (bag instanceof IlvManager) {
        IlvMapLabeler labeler = IlvMapLabelerProperty.GetMapLabeler((IlvManager) bag);
        labeler.performLabeling();
      }
    }

    Override
    public IlvMapStyle getStyle() {
      // if (style == null)
      // style = new MapEllipseStyle();
      return style;
    }

    Override
    public void setStyle(IlvMapStyle style) {
      this.style = (MapEllipseStyle) style;
    }

    Override
    public IlvGraphic copy(IlvCoordinateTransformation transform) throws IlvCoordinateTransformationException {
      IlvRect r = getDefinitionRect();
      IlvPoint p = new IlvPoint(r.x + r.width / 2.0, r.y + r.height / 2.0);
      p = IlvMapUtil.transform(p, transform);
      r = IlvMapUtil.transform(r, transform);
      r.setRect(p.x - r.width / 2.0, p.y - r.height / 2.0, r.width, r.height);
      MapEllipse rtn = (MapEllipse) copy();
      rtn.drawrect.reshape(r.x, r.y, r.width, r.height);
      IlvMapSelectionFactory.setEditable(rtn, true);
      return rtn;
    }

    Override
    public Color getBackground() {
      if (getEllipseStyle() != null)
        return getEllipseStyle().getBackground();
      return null;
    }

    Override
    public Color getForeground() {
      if (getEllipseStyle() != null)
        return getEllipseStyle().getForeground();
      return null;
    }

    Override
    public boolean isFillOn() {
      if (getEllipseStyle() != null)
        return getEllipseStyle().isFill();
      return false;
    }

    Override
    public boolean isStrokeOn() {
      if (getEllipseStyle() != null)
        return getEllipseStyle().isStroke();
      return false;
    }

    Override
    public void setBackground(Color color) {
      if (getEllipseStyle() != null)
        getEllipseStyle().setBackground(color);
    }

    Override
    public void setFillOn(boolean set) {
      if (!set)
        new Throwable().printStackTrace();
      if (getEllipseStyle() != null)
        getEllipseStyle().setFill(set);
    }

    Override
    public void setForeground(Color color) {
      if (getEllipseStyle() != null)
        getEllipseStyle().setForeground(color);
    }

    Override
    public void setStrokeOn(boolean set) {
      if (getEllipseStyle() != null)
        getEllipseStyle().setStroke(set);
    }

    private MapEllipseStyle getEllipseStyle() {
      return (MapEllipseStyle) getStyle();
    }

    Override
    public IlvMapGeometry makeGeometry() {
      IlvRect r = boundingBox();
      IlvCoordinate ul = new IlvCoordinate(r.x, -r.y - r.height);
      IlvCoordinate lr = new IlvCoordinate(r.x + r.width, -r.y);
      IlvMapEllipse geom = new IlvMapEllipse(ul, lr, r.width, r.height);
      return geom;
    }
  }

  /**
   * Style of ellipses
   */
  public static class MapEllipseStyle extends IlvMapStyle {
    /**
     * Line indicator attribute
     */
    public static final String STROKE = "stroke";//$NON-NLS-1$
    /**
     * Fill indicator attribute
     */
    public static final String FILL = "fill"; //$NON-NLS-1$
    /**
     * Foreground color attribute
     */
    public static final String FOREGROUND = "foreground"; //$NON-NLS-1$
    /**
     * Background color attribute
     */
    public static final String BACKGROUND = "background"; //$NON-NLS-1$

    /**
     * Creates a new <code>MapEllipseStyle</code>.
     * 
     * @param stream
     * @throws IlvReadFileException
     */
    public MapEllipseStyle(IlvInputStream stream) throws IlvReadFileException {
      super(stream);
      init();
      try {
        setStroke(stream.readBoolean(STROKE));
      } catch (IlvFieldNotFoundException e) {
        // ignore
      }
      try {
        setFill(stream.readBoolean(FILL));
      } catch (IlvFieldNotFoundException e) {
        // ignore
      }
      try {
        setForeground(stream.readColor(FOREGROUND));
      } catch (IlvFieldNotFoundException e) {/* ignore */
      }
      try {
        setBackground(stream.readColor(BACKGROUND));
      } catch (IlvFieldNotFoundException e) {/* ignore */
      }
    }

    Override
    public void write(IlvOutputStream stream) throws IOException {
      super.write(stream);
      Boolean b = (Boolean) getAttribute(STROKE, false);
      if (b != null)
        stream.write(STROKE, b.booleanValue());
      b = (Boolean) getAttribute(FILL, false);
      if (b != null)
        stream.write(FILL, b.booleanValue());
      Color c = (Color) getAttribute(FOREGROUND);
      if (c != null)
        stream.write(FOREGROUND, c);
      c = (Color) getAttribute(BACKGROUND);
      if (c != null)
        stream.write(BACKGROUND, c);
    }

    /**
     * Creates a new <code>MapEllipseStyle</code>.
     */
    public MapEllipseStyle() {
      super();
      init();
    }

    /**
     * Creates a new <code>MapEllipseStyle</code>.
     * 
     * @param source
     */
    public MapEllipseStyle(MapEllipseStyle source) {
      super(source);
      init();
      setAttribute(FILL, source.getAttribute(FILL, false));
      setAttribute(STROKE, source.getAttribute(STROKE, false));
      setAttribute(FOREGROUND, source.getAttribute(FOREGROUND, false));
      setAttribute(BACKGROUND, source.getAttribute(BACKGROUND, false));
    }

    Override
    public IlvMapStyle copy() {
      return new MapEllipseStyle(this);
    }

    SuppressWarnings("unchecked")
    private void init() {
      getDefaultValues().put(STROKE, Boolean.TRUE);
      getDefaultValues().put(FILL, Boolean.TRUE);
      getDefaultValues().put(FOREGROUND, Color.black);
      getDefaultValues().put(BACKGROUND, null);
    }

    /**
     * @return the background color
     */
    public Color getBackground() {
      return (Color) getAttribute(BACKGROUND, true);
    }

    /**
     * Sets the bakground color
     * 
     * @param c
     */
    public void setBackground(Color c) {
      setAttribute(BACKGROUND, c);
    }

    /**
     * @return the foreground color
     */
    public Color getForeground() {
      return (Color) getAttribute(FOREGROUND, true);
    }

    /**
     * Sets the foreground color
     * 
     * @param c
     */
    public void setForeground(Color c) {
      setAttribute(FOREGROUND, c);
    }

    /**
     * Indicates whether ellipse should be filled
     * 
     * @param fill
     */
    public void setFill(boolean fill) {
      setAttribute(FILL, Boolean.valueOf(fill));
    }

    /**
     * @return whether the ellipse is filled
     */
    public boolean isFill() {
      return ((Boolean) getAttribute(FILL, true)).booleanValue();
    }

    /**
     * Indicates whether ellipse should be stroked
     * 
     * @param stroke
     */
    public void setStroke(boolean stroke) {
      setAttribute(STROKE, Boolean.valueOf(stroke));
    }

    /**
     * @return whether the ellipse is stroked
     */
    public boolean isStroke() {
      return ((Boolean) getAttribute(STROKE, true)).booleanValue();
    }

  }

}