geo map viewer code refactored; object info tooltips on geo map (desktop only currently)
authorVictor Kirhenshtein <victor@netxms.org>
Sat, 3 Oct 2015 21:03:24 +0000 (00:03 +0300)
committerVictor Kirhenshtein <victor@netxms.org>
Sat, 3 Oct 2015 21:03:24 +0000 (00:03 +0300)
20 files changed:
configure.ac
src/java/netxms-eclipse/Dashboard/src/org/netxms/ui/eclipse/dashboard/widgets/GeoMapElement.java
src/java/netxms-eclipse/NetworkMaps/src/org/netxms/ui/eclipse/networkmaps/views/AbstractNetworkMapView.java
src/java/netxms-eclipse/OSM/src/org/netxms/ui/eclipse/osm/views/AbstractGeolocationView.java
src/java/netxms-eclipse/OSM/src/org/netxms/ui/eclipse/osm/views/HistoryView.java
src/java/netxms-eclipse/OSM/src/org/netxms/ui/eclipse/osm/views/LocationMap.java
src/java/netxms-eclipse/OSM/src/org/netxms/ui/eclipse/osm/views/WorldMap.java
src/java/netxms-eclipse/OSM/src/org/netxms/ui/eclipse/osm/widgets/AbstractGeoMapViewer.java [moved from src/java/netxms-eclipse/OSM/src/org/netxms/ui/eclipse/osm/widgets/GeoMapViewer.java with 53% similarity]
src/java/netxms-eclipse/OSM/src/org/netxms/ui/eclipse/osm/widgets/GeoLocationHistoryViewer.java [new file with mode: 0644]
src/java/netxms-eclipse/OSM/src/org/netxms/ui/eclipse/osm/widgets/ObjectGeoLocationViewer.java [new file with mode: 0644]
webui/webapp/Dashboard/src/org/netxms/ui/eclipse/dashboard/widgets/GeoMapElement.java
webui/webapp/NetworkMaps/src/org/netxms/ui/eclipse/networkmaps/views/AbstractNetworkMapView.java
webui/webapp/OSM/src/org/netxms/ui/eclipse/osm/views/AbstractGeolocationView.java
webui/webapp/OSM/src/org/netxms/ui/eclipse/osm/views/HistoryView.java
webui/webapp/OSM/src/org/netxms/ui/eclipse/osm/views/LocationMap.java
webui/webapp/OSM/src/org/netxms/ui/eclipse/osm/views/WorldMap.java
webui/webapp/OSM/src/org/netxms/ui/eclipse/osm/widgets/AbstractGeoMapViewer.java [new file with mode: 0644]
webui/webapp/OSM/src/org/netxms/ui/eclipse/osm/widgets/GeoLocationHistoryViewer.java [new file with mode: 0644]
webui/webapp/OSM/src/org/netxms/ui/eclipse/osm/widgets/GeoMapViewer.java [deleted file]
webui/webapp/OSM/src/org/netxms/ui/eclipse/osm/widgets/ObjectGeoLocationViewer.java [new file with mode: 0644]

index ef6a458..79de817 100644 (file)
@@ -1559,22 +1559,22 @@ if test $? = 0; then
                        AC_CHECK_LIB(sqlite3, sqlite3_initialize, [], [ HAVE_SQLITE=no ])
                fi
                if test "x$HAVE_SQLITE" = "xyes"; then
-      if test "x$cross_compiling" = "xyes"; then
-        AC_MSG_WARN([Cross-compiling mode is active, assuming libsqlite3 is thread-safe])
-      else
-        AC_MSG_CHECKING(if libsqlite3 is thread-safe)
-        AC_RUN_IFELSE([AC_LANG_PROGRAM([[
-          #include <sqlite3.h>
-        ]],[[
-          return sqlite3_threadsafe() ? 0 : 1;
-        ]])
-        ],[
-          AC_MSG_RESULT(yes)
-        ],[
-          AC_MSG_RESULT(no)
-          HAVE_SQLITE=no
-        ])
-      fi
+                       if test "x$cross_compiling" = "xyes"; then
+                               AC_MSG_WARN([Cross-compiling mode is active, assuming libsqlite3 is thread-safe])
+                       else
+                               AC_MSG_CHECKING(if libsqlite3 is thread-safe)
+                               AC_RUN_IFELSE([AC_LANG_PROGRAM([[
+#include <sqlite3.h>
+                                       ]],[[
+                                               return sqlite3_threadsafe() ? 0 : 1;
+                                       ]])
+                               ],[
+                                       AC_MSG_RESULT(yes)
+                               ],[
+                                       AC_MSG_RESULT(no)
+                                       HAVE_SQLITE=no
+                               ])
+                       fi
                fi
                LIBS="$OLD_LIBS"
        fi
index eac9441..7392456 100644 (file)
@@ -23,7 +23,7 @@ import org.eclipse.swt.layout.FillLayout;
 import org.eclipse.ui.IViewPart;
 import org.netxms.client.dashboards.DashboardElement;
 import org.netxms.ui.eclipse.dashboard.widgets.internal.GeoMapConfig;
-import org.netxms.ui.eclipse.osm.widgets.GeoMapViewer;
+import org.netxms.ui.eclipse.osm.widgets.ObjectGeoLocationViewer;
 
 /**
  * Geo map element for dashboard
@@ -31,7 +31,7 @@ import org.netxms.ui.eclipse.osm.widgets.GeoMapViewer;
  */
 public class GeoMapElement extends ElementWidget
 {
-       private GeoMapViewer mapWidget;
+       private ObjectGeoLocationViewer mapWidget;
        private GeoMapConfig config;
        
        /**
@@ -57,7 +57,7 @@ public class GeoMapElement extends ElementWidget
                layout.marginWidth = 0;
                setLayout(layout);
                
-               mapWidget = new GeoMapViewer(this, SWT.NONE, false, null);
+               mapWidget = new ObjectGeoLocationViewer(this, SWT.NONE);
                mapWidget.setViewPart(viewPart);
                mapWidget.setTitle(config.getTitle());
                mapWidget.showMap(config.getLatitude(), config.getLongitude(), config.getZoom());
index 0561aac..e35be87 100644 (file)
@@ -348,7 +348,7 @@ public abstract class AbstractNetworkMapView extends ViewPart implements ISelect
                                        }
                                }
 
-                               // Default behaviour
+                               // Default behavior
                                actionOpenSubmap.run();
                        }
                });
index c6d98ef..59a7a04 100644 (file)
@@ -44,7 +44,7 @@ import org.netxms.ui.eclipse.console.resources.SharedIcons;
 import org.netxms.ui.eclipse.objectbrowser.api.ObjectContextMenu;
 import org.netxms.ui.eclipse.osm.Messages;
 import org.netxms.ui.eclipse.osm.tools.MapAccessor;
-import org.netxms.ui.eclipse.osm.widgets.GeoMapViewer;
+import org.netxms.ui.eclipse.osm.widgets.AbstractGeoMapViewer;
 import org.netxms.ui.eclipse.osm.widgets.helpers.GeoMapListener;
 import org.netxms.ui.eclipse.shared.ConsoleSharedData;
 
@@ -55,7 +55,7 @@ public abstract class AbstractGeolocationView extends ViewPart implements ISelec
 {
        public static final String JOB_FAMILY = "MapViewJob"; //$NON-NLS-1$
        
-       protected GeoMapViewer map;
+       protected AbstractGeoMapViewer map;
        
        private MapAccessor mapAccessor;
        private int zoomLevel = 15;
@@ -103,7 +103,7 @@ public abstract class AbstractGeolocationView extends ViewPart implements ISelec
        public void createPartControl(Composite parent)
        {
                // Map control
-               map = new GeoMapViewer(parent, SWT.BORDER, false, null);
+               map = createMapViewer(parent, SWT.BORDER);
                map.setViewPart(this);
                
                createActions();
@@ -138,6 +138,15 @@ public abstract class AbstractGeolocationView extends ViewPart implements ISelec
        }
 
        /**
+        * Create actual map viewer control
+        * 
+        * @param parent
+        * @param style
+        * @return
+        */
+       protected abstract AbstractGeoMapViewer createMapViewer(Composite parent, int style);
+       
+       /**
         * Create actions
         */
        protected void createActions()
index 40d94e9..303da5a 100644 (file)
@@ -41,7 +41,7 @@ import org.netxms.ui.eclipse.console.resources.SharedIcons;
 import org.netxms.ui.eclipse.osm.Messages;
 import org.netxms.ui.eclipse.osm.dialogs.TimeSelectionDialog;
 import org.netxms.ui.eclipse.osm.tools.MapAccessor;
-import org.netxms.ui.eclipse.osm.widgets.GeoMapViewer;
+import org.netxms.ui.eclipse.osm.widgets.GeoLocationHistoryViewer;
 import org.netxms.ui.eclipse.osm.widgets.helpers.GeoMapListener;
 import org.netxms.ui.eclipse.shared.ConsoleSharedData;
 
@@ -62,7 +62,7 @@ public class HistoryView extends ViewPart
         Messages.get().HistoryView_Preset2days, Messages.get().HistoryView_Preset5days, Messages.get().HistoryView_Preset1week, Messages.get().HistoryView_Preset1month,Messages.get().HistoryView_Preset1year };
 
        
-       protected GeoMapViewer map;
+       protected GeoLocationHistoryViewer map;
        
        private MapAccessor mapAccessor;
        private int zoomLevel = 15;
@@ -130,7 +130,7 @@ public class HistoryView extends ViewPart
        public void createPartControl(Composite parent)
        {
                // Map control
-               map = new GeoMapViewer(parent, SWT.BORDER, true, object);
+               map = new GeoLocationHistoryViewer(parent, SWT.BORDER, object);
                map.setViewPart(this);
                
                createActions(parent);
index 97a4c21..64f439f 100644 (file)
  */
 package org.netxms.ui.eclipse.osm.views;
 
+import org.eclipse.swt.widgets.Composite;
 import org.eclipse.ui.IViewSite;
 import org.eclipse.ui.PartInitException;
 import org.netxms.base.GeoLocation;
 import org.netxms.client.NXCSession;
 import org.netxms.client.objects.AbstractObject;
 import org.netxms.ui.eclipse.osm.Messages;
+import org.netxms.ui.eclipse.osm.widgets.AbstractGeoMapViewer;
+import org.netxms.ui.eclipse.osm.widgets.ObjectGeoLocationViewer;
 import org.netxms.ui.eclipse.shared.ConsoleSharedData;
 
 /**
@@ -58,6 +61,15 @@ public class LocationMap extends AbstractGeolocationView
        }
 
        /* (non-Javadoc)
+    * @see org.netxms.ui.eclipse.osm.views.AbstractGeolocationView#createMapViewer(org.eclipse.swt.widgets.Composite, int)
+    */
+   @Override
+   protected AbstractGeoMapViewer createMapViewer(Composite parent, int style)
+   {
+      return new ObjectGeoLocationViewer(parent, style);
+   }
+
+   /* (non-Javadoc)
         * @see org.netxms.ui.eclipse.osm.views.AbstractGeolocationView#getInitialCenterPoint()
         */
        @Override
index 7c1e9ac..3c3b358 100644 (file)
@@ -1,6 +1,6 @@
 /**
  * NetXMS - open source network management system
- * Copyright (C) 2003-2013 Victor Kirhenshtein
+ * Copyright (C) 2003-2015 Victor Kirhenshtein
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -23,6 +23,7 @@ import org.eclipse.jface.action.Action;
 import org.eclipse.jface.action.IMenuManager;
 import org.eclipse.jface.action.Separator;
 import org.eclipse.jface.window.Window;
+import org.eclipse.swt.widgets.Composite;
 import org.eclipse.ui.IMemento;
 import org.eclipse.ui.IViewSite;
 import org.eclipse.ui.PartInitException;
@@ -34,6 +35,8 @@ import org.netxms.ui.eclipse.objectbrowser.dialogs.ObjectSelectionDialog;
 import org.netxms.ui.eclipse.osm.Activator;
 import org.netxms.ui.eclipse.osm.Messages;
 import org.netxms.ui.eclipse.osm.tools.MapAccessor;
+import org.netxms.ui.eclipse.osm.widgets.AbstractGeoMapViewer;
+import org.netxms.ui.eclipse.osm.widgets.ObjectGeoLocationViewer;
 import org.netxms.ui.eclipse.shared.ConsoleSharedData;
 
 /**
@@ -66,6 +69,15 @@ public class WorldMap extends AbstractGeolocationView
                super.init(site, memento);
        }
 
+   /* (non-Javadoc)
+    * @see org.netxms.ui.eclipse.osm.views.AbstractGeolocationView#createMapViewer(org.eclipse.swt.widgets.Composite, int)
+    */
+   @Override
+   protected AbstractGeoMapViewer createMapViewer(Composite parent, int style)
+   {
+      return new ObjectGeoLocationViewer(parent, style);
+   }
+
        /* (non-Javadoc)
         * @see org.eclipse.ui.part.ViewPart#saveState(org.eclipse.ui.IMemento)
         */
@@ -1,6 +1,6 @@
 /**
  * NetXMS - open source network management system
- * Copyright (C) 2003-2014 Victor Kirhenshtein
+ * Copyright (C) 2003-2015 Victor Kirhenshtein
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  */
 package org.netxms.ui.eclipse.osm.widgets;
 
-import java.awt.Polygon;
-import java.text.DateFormat;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
 import java.util.HashSet;
-import java.util.List;
 import java.util.Set;
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.jface.resource.JFaceResources;
@@ -35,7 +29,6 @@ import org.eclipse.swt.events.DisposeListener;
 import org.eclipse.swt.events.MouseEvent;
 import org.eclipse.swt.events.MouseListener;
 import org.eclipse.swt.events.MouseMoveListener;
-import org.eclipse.swt.events.MouseTrackAdapter;
 import org.eclipse.swt.events.MouseWheelListener;
 import org.eclipse.swt.events.PaintEvent;
 import org.eclipse.swt.events.PaintListener;
@@ -44,25 +37,17 @@ import org.eclipse.swt.graphics.Font;
 import org.eclipse.swt.graphics.GC;
 import org.eclipse.swt.graphics.Image;
 import org.eclipse.swt.graphics.Point;
-import org.eclipse.swt.graphics.RGB;
 import org.eclipse.swt.graphics.Rectangle;
 import org.eclipse.swt.widgets.Canvas;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Display;
 import org.eclipse.swt.widgets.Event;
 import org.eclipse.swt.widgets.Listener;
-import org.eclipse.swt.widgets.ToolTip;
 import org.eclipse.ui.IViewPart;
 import org.eclipse.ui.model.WorkbenchLabelProvider;
 import org.netxms.base.GeoLocation;
-import org.netxms.client.NXCSession;
-import org.netxms.client.TimePeriod;
-import org.netxms.client.datacollection.GraphSettings;
 import org.netxms.client.objects.AbstractObject;
-import org.netxms.ui.eclipse.console.resources.RegionalSettings;
 import org.netxms.ui.eclipse.console.resources.SharedColors;
-import org.netxms.ui.eclipse.console.resources.SharedIcons;
-import org.netxms.ui.eclipse.console.resources.StatusDisplayInfo;
 import org.netxms.ui.eclipse.jobs.ConsoleJob;
 import org.netxms.ui.eclipse.osm.Activator;
 import org.netxms.ui.eclipse.osm.GeoLocationCache;
@@ -71,53 +56,43 @@ import org.netxms.ui.eclipse.osm.Messages;
 import org.netxms.ui.eclipse.osm.tools.Area;
 import org.netxms.ui.eclipse.osm.tools.MapAccessor;
 import org.netxms.ui.eclipse.osm.tools.MapLoader;
-import org.netxms.ui.eclipse.osm.tools.QuadTree;
 import org.netxms.ui.eclipse.osm.tools.Tile;
 import org.netxms.ui.eclipse.osm.tools.TileSet;
 import org.netxms.ui.eclipse.osm.widgets.helpers.GeoMapListener;
-import org.netxms.ui.eclipse.shared.ConsoleSharedData;
 import org.netxms.ui.eclipse.tools.ColorCache;
-import org.netxms.ui.eclipse.tools.ColorConverter;
+import org.netxms.ui.eclipse.tools.FontTools;
 
 /**
  * This widget shows map retrieved via OpenStreetMap Static Map API
  */
-public class GeoMapViewer extends Canvas implements PaintListener, GeoLocationCacheListener, MouseWheelListener, MouseListener, MouseMoveListener
+public abstract class AbstractGeoMapViewer extends Canvas implements PaintListener, GeoLocationCacheListener, MouseWheelListener, MouseListener, MouseMoveListener
 {
-   private static final int START = 1;
-   private static final int END = 2;
-   private static final String pointInformation[] = {Messages.get().GeoMapViewer_Start, Messages.get().GeoMapViewer_End};
+   protected static final String[] TITLE_FONTS = { "Segoe UI", "Liberation Sans", "DejaVu Sans", "Verdana", "Arial" };
    
-       private static final Color MAP_BACKGROUND = new Color(Display.getCurrent(), 255, 255, 255);
+   protected static final Color BORDER_COLOR = new Color(Display.getCurrent(), 0, 0, 0);
+   
+       protected static final int LABEL_ARROW_HEIGHT = 5;
+       protected static final int LABEL_X_MARGIN = 4;
+       protected static final int LABEL_Y_MARGIN = 4;
+       protected static final int LABEL_SPACING = 4;
+
+   private static final Color MAP_BACKGROUND = new Color(Display.getCurrent(), 255, 255, 255);
    private static final Color INFO_BLOCK_BACKGROUND = new Color(Display.getCurrent(), 0, 0, 0);
    private static final Color INFO_BLOCK_TEXT = new Color(Display.getCurrent(), 255, 255, 255);
-       private static final Color LABEL_BACKGROUND = new Color(Display.getCurrent(), 240, 254, 192);
-       private static final Color LABEL_TEXT = new Color(Display.getCurrent(), 0, 0, 0);
-       private static final Color BORDER_COLOR = new Color(Display.getCurrent(), 0, 0, 0);
-   private static final Color ACTIVE_BORDER_COLOR = new Color(Display.getCurrent(), 255, 255, 255);
-       private static final Color SELECTION_COLOR = new Color(Display.getCurrent(), 0, 0, 255);
-   private static final Color TRACK_COLOR = new Color(Display.getCurrent(), 163, 73, 164);
-       
-       private static final Font TITLE_FONT = new Font(Display.getCurrent(), "Verdana", 10, SWT.BOLD);  //$NON-NLS-1$
-
-       private static final int LABEL_ARROW_HEIGHT = 5;
-       private static final int LABEL_ARROW_OFFSET = 10;
-       private static final int LABEL_X_MARGIN = 4;
-       private static final int LABEL_Y_MARGIN = 4;
-       private static final int LABEL_SPACING = 4;
-
+   private static final Color SELECTION_COLOR = new Color(Display.getCurrent(), 0, 0, 255);
+   
        private static final int DRAG_JITTER = 8;
 
-       private ILabelProvider labelProvider;
-       private Image currentImage = null;
+   protected ColorCache colorCache;
+       protected ILabelProvider labelProvider;
+   protected Area coverage = new Area(0, 0, 0, 0);
+   protected MapAccessor accessor;
+   protected IViewPart viewPart = null;
+   protected Point currentPoint;
+
+   private Image currentImage = null;
        private Image bufferImage = null;
-       private Area coverage = new Area(0, 0, 0, 0);
-       private List<AbstractObject> objects = new ArrayList<AbstractObject>();
-       private AbstractObject currentObject = null;
-       private MapAccessor accessor;
        private MapLoader mapLoader;
-       private IViewPart viewPart = null;
-       private Point currentPoint;
        private Point dragStartPoint = null;
        private Point selectionStartPoint = null;
        private Point selectionEndPoint = null;
@@ -129,33 +104,22 @@ public class GeoMapViewer extends Canvas implements PaintListener, GeoLocationCa
        private Image imageZoomIn;
        private Image imageZoomOut;
        private Rectangle zoomControlRect = null;
-       private boolean historicalData;
-   private List<GeoLocation> history = new ArrayList<GeoLocation>();
-   private QuadTree<GeoLocation> locationTree = new QuadTree<GeoLocation>();
-   private AbstractObject historyObject = null;
-   private TimePeriod timePeriod = new TimePeriod();
-   private int highlightobjectID = -1;
-   private ToolTip toolTip;
-   private ColorCache colorCache;
-   private List<ObjectIcon> objectIcons = new ArrayList<ObjectIcon>();
+   private Font mapTitleFont;
        
        /**
         * @param parent
         * @param style
         */
-       public GeoMapViewer(Composite parent, int style, final boolean historicalData, AbstractObject historyObject)
+       public AbstractGeoMapViewer(Composite parent, int style)
        {
                super(parent, style | SWT.NO_BACKGROUND | SWT.DOUBLE_BUFFERED);
-               this.historicalData = historicalData;
-               if (historicalData)
-               {
-                  this.historyObject = historyObject;             
-               }
                
                colorCache = new ColorCache(this);
                
                imageZoomIn = Activator.getImageDescriptor("icons/map_zoom_in.png").createImage(); //$NON-NLS-1$
       imageZoomOut = Activator.getImageDescriptor("icons/map_zoom_out.png").createImage(); //$NON-NLS-1$
+      
+      mapTitleFont = FontTools.createFont(TITLE_FONTS, 2, SWT.BOLD);
 
                labelProvider = WorkbenchLabelProvider.getDecoratingWorkbenchLabelProvider();
                mapLoader = new MapLoader(getDisplay());
@@ -193,7 +157,7 @@ public class GeoMapViewer extends Canvas implements PaintListener, GeoLocationCa
                        public void widgetDisposed(DisposeEvent e)
                        {
                                labelProvider.dispose();
-                               GeoLocationCache.getInstance().removeListener(GeoMapViewer.this);
+                               GeoLocationCache.getInstance().removeListener(AbstractGeoMapViewer.this);
                                if (bufferImage != null)
                                        bufferImage.dispose();
                                if (currentImage != null)
@@ -201,6 +165,7 @@ public class GeoMapViewer extends Canvas implements PaintListener, GeoLocationCa
                                mapLoader.dispose();
                                imageZoomIn.dispose();
                                imageZoomOut.dispose();
+                               mapTitleFont.dispose();
                        }
                });
 
@@ -209,102 +174,8 @@ public class GeoMapViewer extends Canvas implements PaintListener, GeoLocationCa
                addMouseWheelListener(this);
 
                GeoLocationCache.getInstance().addListener(this);
-               addMouseTrackListener(new MouseTrackAdapter() {
-         @Override
-         public void mouseHover(MouseEvent e)
-         {
-            highlightobjectID = -1;
-            toolTip.setVisible(false);
-            if (historicalData)
-            {
-               List<GeoLocation> suitablePoints = getAdjacentLocations(e.x, e.y);
-               if (suitablePoints.isEmpty())
-                  return;
-               
-               highlightobjectID = history.indexOf(suitablePoints.get(0)); 
-               redraw();
-            }
-            else
-            {
-               AbstractObject object = getObjectAtPoint(new Point(e.x, e.y));
-               if (object != currentObject)
-                  setCurrentObject(object);
-            }
-         }
-
-         /* (non-Javadoc)
-          * @see org.eclipse.swt.events.MouseTrackAdapter#mouseExit(org.eclipse.swt.events.MouseEvent)
-          */
-         @Override
-         public void mouseExit(MouseEvent e)
-         {
-            highlightobjectID = -1;
-            toolTip.setVisible(false);
-            redraw();
-         }
-      });
-               
-               addMouseMoveListener(new MouseMoveListener() {
-         @Override
-         public void mouseMove(MouseEvent e)
-         {
-            if (highlightobjectID != -1)
-            {
-               highlightobjectID = -1;
-               toolTip.setVisible(false);
-               redraw();
-            }
-         }
-      });
-               
-      toolTip = new ToolTip(getShell(), SWT.BALLOON);  
-       }
-       
-   /**
-    * Get geolocations adjacent to given screen coordinates ordered by distance from that point
-    * 
-    * @param p
-    * @return
-    */
-   public List<GeoLocation> getAdjacentLocations(Point p)
-   {
-      return getAdjacentLocations(p.x, p.y);
-   }
-   
-       /**
-        * Get geolocations adjacent to given screen coordinates ordered by distance from that point
-        * 
-        * @param x
-        * @param y
-        * @return
-        */
-       public List<GeoLocation> getAdjacentLocations(int x, int y)
-       {
-      Point p = new Point(x, y);
-      final GeoLocation center = getLocationAtPoint(p);
-      
-      p.x -= 5;
-      p.y -= 5;
-      GeoLocation topLeft = getLocationAtPoint(p);
-      p.x += 10;
-      p.y += 10;
-      GeoLocation bottomRight = getLocationAtPoint(p);
-      Area area = new Area(topLeft.getLatitude(), topLeft.getLongitude(), bottomRight.getLatitude(), bottomRight.getLongitude());
-
-      List<GeoLocation> locations = locationTree.query(area);
-      Collections.sort(locations, new Comparator<GeoLocation>() {
-         @Override
-         public int compare(GeoLocation l1, GeoLocation l2)
-         {
-            double d1 = Math.pow(Math.pow(l1.getLatitude() - center.getLatitude(), 2) + Math.pow(l1.getLongitude() - center.getLongitude(), 2), 0.5);  
-            double d2 = Math.pow(Math.pow(l2.getLatitude() - center.getLatitude(), 2) + Math.pow(l2.getLongitude() - center.getLongitude(), 2), 0.5);  
-            return (int)Math.signum(d1 - d2);
-         }
-      });
-      
-      return locations;
        }
-       
+               
        /**
         * Add zoom level change listener
         * 
@@ -391,6 +262,9 @@ public class GeoMapViewer extends Canvas implements PaintListener, GeoLocationCa
                                        @Override
                                        public void run()
                                        {
+                  if (isDisposed())
+                     return;
+                  
                                                currentTileSet = null;
                                                if (tiles != null)
                                                {
@@ -408,15 +282,7 @@ public class GeoMapViewer extends Canvas implements PaintListener, GeoLocationCa
                                                
                                                Point mapSize = new Point(currentImage.getImageData().width, currentImage.getImageData().height);
                   coverage = GeoLocationCache.calculateCoverage(mapSize, accessor.getCenterPoint(), GeoLocationCache.CENTER, accessor.getZoom());
-                                               if (!historicalData)
-                                               {
-                                               objects = GeoLocationCache.getInstance().getObjectsInArea(coverage);
-                                               GeoMapViewer.this.redraw();
-                                               }
-                                               else
-                                               {
-                                                  GeoMapViewer.this.updateHistory();
-                                               }
+                  onMapLoad();
                                        }
                                });
                        }
@@ -432,6 +298,11 @@ public class GeoMapViewer extends Canvas implements PaintListener, GeoLocationCa
        }
        
        /**
+        * Map load handler. Called on UI thread after map was (re)loaded.
+        */
+       protected abstract void onMapLoad();
+       
+       /**
         * Load missing tiles in tile set
         * 
         * @param tiles
@@ -446,10 +317,10 @@ public class GeoMapViewer extends Canvas implements PaintListener, GeoLocationCa
                                        @Override
                                        public void run()
                                        {
-                                               if (!GeoMapViewer.this.isDisposed() && (currentTileSet == tiles))
+                                               if (!AbstractGeoMapViewer.this.isDisposed() && (currentTileSet == tiles))
                                                {
                                                        drawTiles(tiles);
-                                                       GeoMapViewer.this.redraw();
+                                                       AbstractGeoMapViewer.this.redraw();
                                                }
                                                tiles.dispose();
                                        }
@@ -516,8 +387,6 @@ public class GeoMapViewer extends Canvas implements PaintListener, GeoLocationCa
       gc.setAntialias(SWT.ON);
       gc.setTextAntialias(SWT.ON);
       
-      objectIcons.clear();
-               
                GeoLocation currentLocation;
 
                // Draw objects and decorations if user is not dragging map
@@ -539,64 +408,7 @@ public class GeoMapViewer extends Canvas implements PaintListener, GeoLocationCa
                 imgH = -1;
              }
 
-                       final Point centerXY = GeoLocationCache.coordinateToDisplay(currentLocation, accessor.getZoom());
-         if (!historicalData)
-         {
-                       for(AbstractObject object : objects)
-                       {
-                               final Point virtualXY = GeoLocationCache.coordinateToDisplay(object.getGeolocation(), accessor.getZoom());
-                               final int dx = virtualXY.x - centerXY.x;
-                               final int dy = virtualXY.y - centerXY.y;
-                               drawObject(gc, imgW / 2 + dx, imgH / 2 + dy, object);
-                       }
-         }
-         else
-         {  
-            int nextX = 0;
-            int nextY = 0;
-            for(int i = 0; i < history.size(); i++)
-            {
-               final Point virtualXY = GeoLocationCache.coordinateToDisplay(history.get(i), accessor.getZoom());
-               final int dx = virtualXY.x - centerXY.x;
-               final int dy = virtualXY.y - centerXY.y;
-               
-               if (i != history.size() - 1)
-               { 
-                  final Point virtualXY2 = GeoLocationCache.coordinateToDisplay(history.get(i + 1), accessor.getZoom());
-                  nextX = imgW / 2 + (virtualXY2.x - centerXY.x);
-                  nextY = imgH / 2 + (virtualXY2.y - centerXY.y);
-               }
-               
-               int color = SWT.COLOR_RED;
-               if (i == highlightobjectID)
-               {
-                  color = SWT.COLOR_GREEN;
-                  DateFormat df = RegionalSettings.getDateTimeFormat();
-                  toolTip.setText(String.format("%s\r\n%s - %s",  //$NON-NLS-1$
-                        history.get(i), df.format(history.get(i).getTimestamp()), df.format(history.get(i).getEndTimestamp())));
-                  toolTip.setVisible(true);
-               }
-                  
-               if (i == 0)
-               {
-                  if (i == history.size() - 1)
-                  {
-                     nextX = imgW / 2 + dx;
-                     nextY = imgH / 2 + dy;
-                  }
-                  drawObject(gc, imgW / 2 + dx, imgH / 2 + dy, GeoMapViewer.START, nextX, nextY, color);                  
-                  continue;
-               } 
-               
-               if (i == history.size() - 1)
-               {    
-                  drawObject(gc, imgW / 2 + dx, imgH / 2 + dy, GeoMapViewer.END, nextX, nextY, color);
-                  continue;
-               }
-               
-               drawObject(gc, imgW / 2 + dx, imgH / 2 + dy, 0, nextX, nextY, color);
-            }
-         }
+             drawContent(gc, currentLocation, imgW, imgH);
                }
                else
                {
@@ -663,7 +475,7 @@ public class GeoMapViewer extends Canvas implements PaintListener, GeoLocationCa
                // Draw title
                if ((title != null) && !title.isEmpty())
                {
-                       gc.setFont(TITLE_FONT);
+                       gc.setFont(mapTitleFont);
                        rect = getClientArea();
                        int x = (rect.width - gc.textExtent(title).x) / 2;
                        gc.setForeground(SharedColors.getColor(SharedColors.GEOMAP_TITLE, getDisplay()));
@@ -695,126 +507,17 @@ public class GeoMapViewer extends Canvas implements PaintListener, GeoLocationCa
       gc.dispose();
       e.gc.drawImage(bufferImage, 0, 0);      
        }
-
+       
        /**
-        * Draw object on map
+        * Draw content over map
         * 
         * @param gc
-        * @param x
-        * @param y
-        * @param object
+        * @param currentLocation current location (map center)
+        * @param imgW map image width
+        * @param imgH map image height
         */
-       private void drawObject(GC gc, int x, int y, AbstractObject object) 
-       {
-          boolean selected = (currentObject != null) && (currentObject.getObjectId() == object.getObjectId());
-          
-               Image image = labelProvider.getImage(object);
-               if (image == null)
-                       image = SharedIcons.IMG_UNKNOWN_OBJECT;
-               
-               int w = image.getImageData().width + LABEL_X_MARGIN * 2;
-               int h = image.getImageData().height+ LABEL_Y_MARGIN * 2;
-               Rectangle rect = new Rectangle(x - w / 2 - 1, y - LABEL_ARROW_HEIGHT - h, w, h);
-               
-               Color bgColor = ColorConverter.adjustColor(StatusDisplayInfo.getStatusColor(object.getStatus()), new RGB(255, 255, 255),  0.5f, colorCache);
-               
-      gc.setBackground(bgColor);
-      gc.fillRoundRectangle(rect.x, rect.y, rect.width, rect.height, 4, 4);
-      if (selected)
-      {
-         gc.setLineWidth(3);
-         gc.setForeground(ACTIVE_BORDER_COLOR);
-         gc.drawRoundRectangle(rect.x, rect.y, rect.width, rect.height, 4, 4);
-      }
-      gc.setLineWidth(1);
-      gc.setForeground(BORDER_COLOR);
-               gc.drawRoundRectangle(rect.x, rect.y, rect.width, rect.height, 4, 4);
-               
-               final int[] arrow = new int[] { x - 4, rect.y + rect.height, x, y, x + 4, rect.y + rect.height };
+       protected abstract void drawContent(GC gc, GeoLocation currentLocation, int imgW, int imgH);
 
-      gc.fillPolygon(arrow);
-      gc.setForeground(bgColor);
-      gc.drawPolygon(arrow);
-      if (selected)
-      {
-         gc.drawLine(arrow[0], arrow[1] - 1, arrow[4], arrow[5] - 1);
-         gc.setLineWidth(3);
-         gc.setForeground(ACTIVE_BORDER_COLOR);
-         gc.drawPolyline(arrow);
-      }
-               gc.setLineWidth(1);
-               gc.setForeground(BORDER_COLOR);
-               gc.drawPolyline(arrow);
-
-               gc.drawImage(image, rect.x + LABEL_X_MARGIN, rect.y + LABEL_Y_MARGIN);
-               
-               objectIcons.add(new ObjectIcon(object, rect, x, y));
-       }
-
-         /**
-    * Draw object on map
-    * 
-    * @param gc
-    * @param x
-    * @param y
-    * @param object
-    */
-   private void drawObject(GC gc, int x, int y, int flag, int prevX, int prevY, int color) 
-   {    
-      if (flag == GeoMapViewer.START || flag == GeoMapViewer.END)
-      {
-         if (flag == GeoMapViewer.START)
-         {
-            gc.setForeground(TRACK_COLOR);
-            gc.setLineWidth(3);
-            gc.drawLine(x, y, prevX, prevY);
-         }
-         
-         gc.setBackground(Display.getCurrent().getSystemColor(color)); 
-         gc.fillOval(x - 5, y -5, 10, 10);
-         
-         final String text = pointInformation[flag -1];
-         final Point textSize = gc.textExtent(text);
-         
-         Rectangle rect = new Rectangle(x - LABEL_ARROW_OFFSET, y - LABEL_ARROW_HEIGHT - textSize.y, textSize.x
-               + LABEL_X_MARGIN * 2 + LABEL_SPACING, textSize.y + LABEL_Y_MARGIN * 2);
-         
-         gc.setBackground(LABEL_BACKGROUND);
-
-         gc.setForeground(BORDER_COLOR);
-         gc.setLineWidth(4);
-         gc.fillRoundRectangle(rect.x, rect.y, rect.width, rect.height, 8, 8);
-         gc.drawRoundRectangle(rect.x, rect.y, rect.width, rect.height, 8, 8);
-         
-         gc.setLineWidth(2);
-         gc.fillRoundRectangle(rect.x, rect.y, rect.width, rect.height, 8, 8);
-         gc.drawRoundRectangle(rect.x, rect.y, rect.width, rect.height, 8, 8);
-         final int[] arrow = new int[] { rect.x + LABEL_ARROW_OFFSET - 4, rect.y + rect.height, x, y, rect.x + LABEL_ARROW_OFFSET + 4,
-               rect.y + rect.height };
-
-         gc.setLineWidth(4);
-         gc.setForeground(BORDER_COLOR);
-         gc.drawPolyline(arrow);
-
-         gc.fillPolygon(arrow);
-         gc.setForeground(LABEL_BACKGROUND);
-         gc.setLineWidth(2);
-         gc.drawLine(arrow[0], arrow[1], arrow[4], arrow[5]);
-         gc.drawPolyline(arrow);
-
-         gc.setForeground(LABEL_TEXT);
-         gc.drawText(text, rect.x + LABEL_X_MARGIN + LABEL_SPACING, rect.y + LABEL_Y_MARGIN);
-      }
-      else 
-      {
-         gc.setForeground(TRACK_COLOR);
-         gc.setLineWidth(3);
-         gc.drawLine(x, y, prevX, prevY);
-         gc.setBackground(Display.getCurrent().getSystemColor(color)); 
-         gc.fillOval(x - 5, y -5, 10, 10);
-      }      
-   }   
-       
        /*
         * (non-Javadoc)
         * 
@@ -829,15 +532,7 @@ public class GeoMapViewer extends Canvas implements PaintListener, GeoLocationCa
                        @Override
                        public void run()
                        {
-                          if (!historicalData)
-                          {
-                             onCacheChange(object, prevLocation);
-                          }
-                          else
-                          {
-                             if (object.getObjectId() == historyObject.getObjectId())
-                                onCacheChange(object, prevLocation);
-                          }
+                     onCacheChange(object, prevLocation);
                        }
                });
        }
@@ -848,25 +543,7 @@ public class GeoMapViewer extends Canvas implements PaintListener, GeoLocationCa
         * @param object
         * @param prevLocation
         */
-       private void onCacheChange(final AbstractObject object, final GeoLocation prevLocation)
-       {
-               GeoLocation currLocation = object.getGeolocation();
-               if (((currLocation.getType() != GeoLocation.UNSET) && 
-                     coverage.contains(currLocation.getLatitude(), currLocation.getLongitude()))
-                               || ((prevLocation != null) && (prevLocation.getType() != GeoLocation.UNSET) && 
-                                     coverage.contains(prevLocation.getLatitude(), prevLocation.getLongitude())))
-               {
-                  if (!historicalData)
-                  {
-                       objects = GeoLocationCache.getInstance().getObjectsInArea(coverage);
-                       redraw();
-                  }
-                  else
-                  {
-                     updateHistory();
-                  }
-               }
-       }
+       protected abstract void onCacheChange(final AbstractObject object, final GeoLocation prevLocation);
        
        /* (non-Javadoc)
         * @see org.eclipse.swt.events.MouseWheelListener#mouseScrolled(org.eclipse.swt.events.MouseEvent)
@@ -949,12 +626,6 @@ public class GeoMapViewer extends Canvas implements PaintListener, GeoLocationCa
                }
 
                currentPoint = new Point(e.x, e.y);
-               if (e.button != 1)
-               {
-               AbstractObject object = getObjectAtPoint(currentPoint);
-               if (object != currentObject)
-                  setCurrentObject(object);
-               }
        }
 
        /* (non-Javadoc)
@@ -1073,23 +744,6 @@ public class GeoMapViewer extends Canvas implements PaintListener, GeoLocationCa
        }
        
        /**
-    * Get object at given point within widget
-    * 
-    * @param p widget-related coordinates
-    * @return object at given point or null
-        */
-       public AbstractObject getObjectAtPoint(Point p)
-       {
-          for(int i = objectIcons.size() - 1; i >= 0; i--)
-          {
-             ObjectIcon icon = objectIcons.get(i);
-             if (icon.contains(p))
-                return icon.object;
-          }
-          return null;
-       }
-
-       /**
         * @return the viewPart
         */
        public IViewPart getViewPart()
@@ -1120,108 +774,12 @@ public class GeoMapViewer extends Canvas implements PaintListener, GeoLocationCa
        {
                this.title = title;
        }
-       
-       /**
-        * Updates points for historical view
-        */
-       private void updateHistory()
-       {
-       final NXCSession session = (NXCSession)ConsoleSharedData.getSession();
-          ConsoleJob job = new ConsoleJob(Messages.get().GeoMapViewer_DownloadJob_Title, viewPart, Activator.PLUGIN_ID, null) {
-         @Override
-         protected void runInternal(IProgressMonitor monitor) throws Exception
-         {
-            history = session.getLocationHistory(historyObject.getObjectId(), timePeriod.getPeriodStart(), timePeriod.getPeriodEnd());
-            for(int i = 0; i < history.size(); i++)
-               locationTree.insert(history.get(i).getLatitude(), history.get(i).getLongitude(), history.get(i));
-            
-            runInUIThread(new Runnable() {
-               @Override
-               public void run()
-               {
-                  GeoMapViewer.this.redraw();
-               }
-            });
-         }
-
-         @Override
-         protected String getErrorMessage()
-         {
-            return Messages.get().GeoMapViewer_DownloadError;
-         }
-      };
-      job.setUser(false);
-      job.start();
-       }
-       
-       /**
-        * Sets new time period
-        */
-   public void setTimePeriod(TimePeriod timePeriod)
-   {
-      this.timePeriod = timePeriod;
-      updateHistory();      
-   }
 
    /**
-    * Gets time period
-    */
-   public TimePeriod getTimePeriod()
-   {
-      return timePeriod;
-   }
-
-   /**
-    * @param value
-    * @param unit
-    */
-   public void changeTimePeriod(int value, int unit)
-   {
-      timePeriod.setTimeFrameType(GraphSettings.TIME_FRAME_BACK_FROM_NOW);
-      timePeriod.setTimeRangeValue(value);
-      timePeriod.setTimeUnitValue(unit);
-      updateHistory();
-   }
-   
-   /**
-    * Set current object
+    * Get object at given point within widget
     * 
-    * @param object
-    */
-   private void setCurrentObject(AbstractObject object)
-   {
-      currentObject = object;
-      if (currentObject != null)
-      {
-         int idx = objects.indexOf(currentObject);
-         objects.remove(idx);
-         objects.add(currentObject);
-      }
-      redraw();
-   }
-
-   /**
-    * Object icon on map
+    * @param p widget-related coordinates
+    * @return object at given point or null
     */
-   private class ObjectIcon
-   {
-      public Rectangle rect;
-      public Polygon arrow;
-      public AbstractObject object;
-
-      public ObjectIcon(AbstractObject object, Rectangle rect, int x, int y)
-      {
-         this.rect = rect;
-         this.object = object;
-         arrow = new Polygon();
-         arrow.addPoint(x, y);
-         arrow.addPoint(x - 4, rect.y + rect.height);
-         arrow.addPoint(x + 4, rect.y + rect.height);
-      }
-      
-      public boolean contains(Point p)
-      {
-         return rect.contains(p) || arrow.contains(p.x, p.y); 
-      }
-   }
+   public abstract AbstractObject getObjectAtPoint(Point p);
 }
diff --git a/src/java/netxms-eclipse/OSM/src/org/netxms/ui/eclipse/osm/widgets/GeoLocationHistoryViewer.java b/src/java/netxms-eclipse/OSM/src/org/netxms/ui/eclipse/osm/widgets/GeoLocationHistoryViewer.java
new file mode 100644 (file)
index 0000000..96506af
--- /dev/null
@@ -0,0 +1,390 @@
+/**
+ * NetXMS - open source network management system
+ * Copyright (C) 2003-2015 Victor Kirhenshtein
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package org.netxms.ui.eclipse.osm.widgets;
+
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.MouseTrackListener;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.ToolTip;
+import org.netxms.base.GeoLocation;
+import org.netxms.client.NXCSession;
+import org.netxms.client.TimePeriod;
+import org.netxms.client.datacollection.GraphSettings;
+import org.netxms.client.objects.AbstractObject;
+import org.netxms.ui.eclipse.console.resources.RegionalSettings;
+import org.netxms.ui.eclipse.jobs.ConsoleJob;
+import org.netxms.ui.eclipse.osm.Activator;
+import org.netxms.ui.eclipse.osm.GeoLocationCache;
+import org.netxms.ui.eclipse.osm.Messages;
+import org.netxms.ui.eclipse.osm.tools.Area;
+import org.netxms.ui.eclipse.osm.tools.QuadTree;
+import org.netxms.ui.eclipse.shared.ConsoleSharedData;
+
+/**
+ * Geo location viewer for object location history
+ */
+public class GeoLocationHistoryViewer extends AbstractGeoMapViewer implements MouseTrackListener
+{
+   private static final int START = 1;
+   private static final int END = 2;
+   private static final String pointInformation[] = { Messages.get().GeoMapViewer_Start, Messages.get().GeoMapViewer_End };
+   
+   private static final int LABEL_ARROW_OFFSET = 10;
+   
+   private static final Color LABEL_BACKGROUND = new Color(Display.getCurrent(), 240, 254, 192);
+   private static final Color LABEL_TEXT = new Color(Display.getCurrent(), 0, 0, 0);
+   private static final Color TRACK_COLOR = new Color(Display.getCurrent(), 163, 73, 164);
+   
+   private AbstractObject historyObject = null;
+   private List<GeoLocation> points = new ArrayList<GeoLocation>();
+   private TimePeriod timePeriod = new TimePeriod();
+   private ToolTip pointToolTip = null;
+   private QuadTree<GeoLocation> locationTree = new QuadTree<GeoLocation>();
+   private int selectedPoint = -1;
+
+   /**
+    * @param parent
+    * @param style
+    * @param object
+    */
+   public GeoLocationHistoryViewer(Composite parent, int style, AbstractObject object)
+   {
+      super(parent, style);
+      this.historyObject = object;
+      pointToolTip = new ToolTip(getShell(), SWT.BALLOON);
+      addMouseTrackListener(this);
+   }
+
+   /*
+    * (non-Javadoc)
+    * 
+    * @see org.netxms.ui.eclipse.osm.widgets.AbstractGeoMapViewer#onMapLoad()
+    */
+   @Override
+   protected void onMapLoad()
+   {
+      updateHistory();
+   }
+
+   /* (non-Javadoc)
+    * @see org.netxms.ui.eclipse.osm.widgets.AbstractGeoMapViewer#onCacheChange(org.netxms.client.objects.AbstractObject, org.netxms.base.GeoLocation)
+    */
+   @Override
+   protected void onCacheChange(AbstractObject object, GeoLocation prevLocation)
+   {
+      if (object.getObjectId() == historyObject.getObjectId())
+         updateHistory();
+   }
+
+   /* (non-Javadoc)
+    * @see org.eclipse.swt.events.MouseTrackListener#mouseHover(org.eclipse.swt.events.MouseEvent)
+    */
+   @Override
+   public void mouseHover(MouseEvent e)
+   {
+      selectedPoint = -1;
+      pointToolTip.setVisible(false);
+      List<GeoLocation> suitablePoints = getAdjacentLocations(e.x, e.y);
+      if (suitablePoints.isEmpty())
+         return;
+      
+      selectedPoint = points.indexOf(suitablePoints.get(0)); 
+      redraw();
+   }
+
+   /* (non-Javadoc)
+    * @see org.eclipse.swt.events.MouseTrackListener#mouseEnter(org.eclipse.swt.events.MouseEvent)
+    */
+   @Override
+   public void mouseEnter(MouseEvent e)
+   {
+   }
+
+   /* (non-Javadoc)
+    * @see org.eclipse.swt.events.MouseTrackListener#mouseExit(org.eclipse.swt.events.MouseEvent)
+    */
+   @Override
+   public void mouseExit(MouseEvent e)
+   {
+      selectedPoint = -1;
+      pointToolTip.setVisible(false);
+      redraw();
+   }
+
+   /* (non-Javadoc)
+    * @see org.netxms.ui.eclipse.osm.widgets.AbstractGeoMapViewer#mouseMove(org.eclipse.swt.events.MouseEvent)
+    */
+   @Override
+   public void mouseMove(MouseEvent e)
+   {
+      super.mouseMove(e);
+      if (selectedPoint != -1)
+      {
+         selectedPoint = -1;
+         pointToolTip.setVisible(false);
+         redraw();
+      }
+   }
+
+   /**
+    * Get geolocations adjacent to given screen coordinates ordered by distance from that point
+    * 
+    * @param p
+    * @return
+    */
+   public List<GeoLocation> getAdjacentLocations(Point p)
+   {
+      return getAdjacentLocations(p.x, p.y);
+   }
+   
+   /**
+    * Get geolocations adjacent to given screen coordinates ordered by distance from that point
+    * 
+    * @param x
+    * @param y
+    * @return
+    */
+   public List<GeoLocation> getAdjacentLocations(int x, int y)
+   {
+      Point p = new Point(x, y);
+      final GeoLocation center = getLocationAtPoint(p);
+      
+      p.x -= 5;
+      p.y -= 5;
+      GeoLocation topLeft = getLocationAtPoint(p);
+      p.x += 10;
+      p.y += 10;
+      GeoLocation bottomRight = getLocationAtPoint(p);
+      Area area = new Area(topLeft.getLatitude(), topLeft.getLongitude(), bottomRight.getLatitude(), bottomRight.getLongitude());
+
+      List<GeoLocation> locations = locationTree.query(area);
+      Collections.sort(locations, new Comparator<GeoLocation>() {
+         @Override
+         public int compare(GeoLocation l1, GeoLocation l2)
+         {
+            double d1 = Math.pow(Math.pow(l1.getLatitude() - center.getLatitude(), 2) + Math.pow(l1.getLongitude() - center.getLongitude(), 2), 0.5);  
+            double d2 = Math.pow(Math.pow(l2.getLatitude() - center.getLatitude(), 2) + Math.pow(l2.getLongitude() - center.getLongitude(), 2), 0.5);  
+            return (int)Math.signum(d1 - d2);
+         }
+      });
+      
+      return locations;
+   }
+   
+   /**
+    * Updates points for historical view
+    */
+   private void updateHistory()
+   {
+      final NXCSession session = ConsoleSharedData.getSession();
+      ConsoleJob job = new ConsoleJob(Messages.get().GeoMapViewer_DownloadJob_Title, viewPart, Activator.PLUGIN_ID, null) {
+         @Override
+         protected void runInternal(IProgressMonitor monitor) throws Exception
+         {
+            final List<GeoLocation> pl = session.getLocationHistory(historyObject.getObjectId(), timePeriod.getPeriodStart(), timePeriod.getPeriodEnd());
+            runInUIThread(new Runnable() {
+               @Override
+               public void run()
+               {
+                  points = pl;
+                  locationTree.removeAll();
+                  for(int i = 0; i < points.size(); i++)
+                     locationTree.insert(points.get(i).getLatitude(), points.get(i).getLongitude(), points.get(i));
+                  redraw();
+               }
+            });
+         }
+
+         @Override
+         protected String getErrorMessage()
+         {
+            return Messages.get().GeoMapViewer_DownloadError;
+         }
+      };
+      job.setUser(false);
+      job.start();
+   }
+
+   /**
+    * Sets new time period
+    */
+   public void setTimePeriod(TimePeriod timePeriod)
+   {
+      this.timePeriod = timePeriod;
+      updateHistory();      
+   }
+
+   /**
+    * Gets time period
+    */
+   public TimePeriod getTimePeriod()
+   {
+      return timePeriod;
+   }
+
+   /**
+    * @param value
+    * @param unit
+    */
+   public void changeTimePeriod(int value, int unit)
+   {
+      timePeriod.setTimeFrameType(GraphSettings.TIME_FRAME_BACK_FROM_NOW);
+      timePeriod.setTimeRangeValue(value);
+      timePeriod.setTimeUnitValue(unit);
+      updateHistory();
+   }
+
+   /* (non-Javadoc)
+    * @see org.netxms.ui.eclipse.osm.widgets.AbstractGeoMapViewer#drawContent(org.eclipse.swt.graphics.GC, org.netxms.base.GeoLocation, int, int)
+    */
+   @Override
+   protected void drawContent(GC gc, GeoLocation currentLocation, int imgW, int imgH)
+   {
+      final Point centerXY = GeoLocationCache.coordinateToDisplay(currentLocation, accessor.getZoom());
+      int nextX = 0;
+      int nextY = 0;
+      for(int i = 0; i < points.size(); i++)
+      {
+         final Point virtualXY = GeoLocationCache.coordinateToDisplay(points.get(i), accessor.getZoom());
+         final int dx = virtualXY.x - centerXY.x;
+         final int dy = virtualXY.y - centerXY.y;
+         
+         if (i != points.size() - 1)
+         { 
+            final Point virtualXY2 = GeoLocationCache.coordinateToDisplay(points.get(i + 1), accessor.getZoom());
+            nextX = imgW / 2 + (virtualXY2.x - centerXY.x);
+            nextY = imgH / 2 + (virtualXY2.y - centerXY.y);
+         }
+         
+         int color = SWT.COLOR_RED;
+         if (i == selectedPoint)
+         {
+            color = SWT.COLOR_GREEN;
+            DateFormat df = RegionalSettings.getDateTimeFormat();
+            pointToolTip.setText(String.format("%s\r\n%s - %s",  //$NON-NLS-1$
+                  points.get(i), df.format(points.get(i).getTimestamp()), df.format(points.get(i).getEndTimestamp())));
+            pointToolTip.setVisible(true);
+         }
+            
+         if (i == 0)
+         {
+            if (i == points.size() - 1)
+            {
+               nextX = imgW / 2 + dx;
+               nextY = imgH / 2 + dy;
+            }
+            drawPoint(gc, imgW / 2 + dx, imgH / 2 + dy, START, nextX, nextY, color);                  
+            continue;
+         } 
+         
+         if (i == points.size() - 1)
+         {    
+            drawPoint(gc, imgW / 2 + dx, imgH / 2 + dy, END, nextX, nextY, color);
+            continue;
+         }
+         
+         drawPoint(gc, imgW / 2 + dx, imgH / 2 + dy, 0, nextX, nextY, color);
+      }
+   }
+
+   /**
+    * Draw point
+    * 
+    * @param gc
+    * @param x
+    * @param y
+    * @param object
+    */
+   private void drawPoint(GC gc, int x, int y, int flag, int prevX, int prevY, int color) 
+   {    
+      if (flag == START || flag == END)
+      {
+         if (flag == START)
+         {
+            gc.setForeground(TRACK_COLOR);
+            gc.setLineWidth(3);
+            gc.drawLine(x, y, prevX, prevY);
+         }
+         
+         gc.setBackground(getDisplay().getSystemColor(color)); 
+         gc.fillOval(x - 5, y -5, 10, 10);
+         
+         final String text = pointInformation[flag -1];
+         final Point textSize = gc.textExtent(text);
+         
+         Rectangle rect = new Rectangle(x - LABEL_ARROW_OFFSET, y - LABEL_ARROW_HEIGHT - textSize.y, textSize.x
+               + LABEL_X_MARGIN * 2 + LABEL_SPACING, textSize.y + LABEL_Y_MARGIN * 2);
+         
+         gc.setBackground(LABEL_BACKGROUND);
+
+         gc.setForeground(BORDER_COLOR);
+         gc.setLineWidth(4);
+         gc.fillRoundRectangle(rect.x, rect.y, rect.width, rect.height, 8, 8);
+         gc.drawRoundRectangle(rect.x, rect.y, rect.width, rect.height, 8, 8);
+         
+         gc.setLineWidth(2);
+         gc.fillRoundRectangle(rect.x, rect.y, rect.width, rect.height, 8, 8);
+         gc.drawRoundRectangle(rect.x, rect.y, rect.width, rect.height, 8, 8);
+         final int[] arrow = new int[] { rect.x + LABEL_ARROW_OFFSET - 4, rect.y + rect.height, x, y, rect.x + LABEL_ARROW_OFFSET + 4,
+               rect.y + rect.height };
+
+         gc.setLineWidth(4);
+         gc.setForeground(BORDER_COLOR);
+         gc.drawPolyline(arrow);
+
+         gc.fillPolygon(arrow);
+         gc.setForeground(LABEL_BACKGROUND);
+         gc.setLineWidth(2);
+         gc.drawLine(arrow[0], arrow[1], arrow[4], arrow[5]);
+         gc.drawPolyline(arrow);
+
+         gc.setForeground(LABEL_TEXT);
+         gc.drawText(text, rect.x + LABEL_X_MARGIN + LABEL_SPACING, rect.y + LABEL_Y_MARGIN);
+      }
+      else 
+      {
+         gc.setForeground(TRACK_COLOR);
+         gc.setLineWidth(3);
+         gc.drawLine(x, y, prevX, prevY);
+         gc.setBackground(Display.getCurrent().getSystemColor(color)); 
+         gc.fillOval(x - 5, y -5, 10, 10);
+      }      
+   }  
+
+   /* (non-Javadoc)
+    * @see org.netxms.ui.eclipse.osm.widgets.AbstractGeoMapViewer#getObjectAtPoint(org.eclipse.swt.graphics.Point)
+    */
+   @Override
+   public AbstractObject getObjectAtPoint(Point p)
+   {
+      return null;
+   }
+}
diff --git a/src/java/netxms-eclipse/OSM/src/org/netxms/ui/eclipse/osm/widgets/ObjectGeoLocationViewer.java b/src/java/netxms-eclipse/OSM/src/org/netxms/ui/eclipse/osm/widgets/ObjectGeoLocationViewer.java
new file mode 100644 (file)
index 0000000..0e67b08
--- /dev/null
@@ -0,0 +1,401 @@
+/**
+ * NetXMS - open source network management system
+ * Copyright (C) 2003-2015 Victor Kirhenshtein
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package org.netxms.ui.eclipse.osm.widgets;
+
+import java.awt.Polygon;
+import java.util.ArrayList;
+import java.util.List;
+import org.eclipse.jface.resource.JFaceResources;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.DisposeEvent;
+import org.eclipse.swt.events.DisposeListener;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.MouseTrackListener;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.RGB;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.netxms.base.GeoLocation;
+import org.netxms.client.objects.AbstractObject;
+import org.netxms.ui.eclipse.console.resources.SharedIcons;
+import org.netxms.ui.eclipse.console.resources.StatusDisplayInfo;
+import org.netxms.ui.eclipse.osm.GeoLocationCache;
+import org.netxms.ui.eclipse.tools.ColorConverter;
+import org.netxms.ui.eclipse.tools.FontTools;
+
+/**
+ * Geo location viewer for objects
+ */
+public class ObjectGeoLocationViewer extends AbstractGeoMapViewer implements MouseTrackListener
+{
+   private static final int OBJECT_TOOLTIP_X_MARGIN = 6;
+   private static final int OBJECT_TOOLTIP_Y_MARGIN = 6;
+   private static final int OBJECT_TOOLTIP_SPACING = 6;
+
+   private static final Color ACTIVE_BORDER_COLOR = new Color(Display.getCurrent(), 255, 255, 255);
+   
+   private List<AbstractObject> objects = new ArrayList<AbstractObject>();
+   private AbstractObject currentObject = null;
+   private List<ObjectIcon> objectIcons = new ArrayList<ObjectIcon>();
+   private Point objectToolTipLocation = null;
+   private Rectangle objectTooltipRectangle = null;
+   private Font objectToolTipHeaderFont;
+   
+   /**
+    * @param parent
+    * @param style
+    */
+   public ObjectGeoLocationViewer(Composite parent, int style)
+   {
+      super(parent, style);
+      addMouseTrackListener(this);
+
+      objectToolTipHeaderFont = FontTools.createFont(TITLE_FONTS, 1, SWT.BOLD);
+      
+      addDisposeListener(new DisposeListener() {
+         @Override
+         public void widgetDisposed(DisposeEvent e)
+         {
+            objectToolTipHeaderFont.dispose();
+         }
+      });
+   }
+
+   /* (non-Javadoc)
+    * @see org.netxms.ui.eclipse.osm.widgets.AbstractGeoMapViewer#onMapLoad()
+    */
+   @Override
+   protected void onMapLoad()
+   {
+      objects = GeoLocationCache.getInstance().getObjectsInArea(coverage);
+      redraw();
+   }
+
+   /* (non-Javadoc)
+    * @see org.netxms.ui.eclipse.osm.widgets.AbstractGeoMapViewer#onCacheChange(org.netxms.client.objects.AbstractObject, org.netxms.base.GeoLocation)
+    */
+   @Override
+   protected void onCacheChange(AbstractObject object, GeoLocation prevLocation)
+   {
+      GeoLocation currLocation = object.getGeolocation();
+      if (((currLocation.getType() != GeoLocation.UNSET) && 
+            coverage.contains(currLocation.getLatitude(), currLocation.getLongitude()))
+            || ((prevLocation != null) && (prevLocation.getType() != GeoLocation.UNSET) && 
+                  coverage.contains(prevLocation.getLatitude(), prevLocation.getLongitude())))
+      {
+         objects = GeoLocationCache.getInstance().getObjectsInArea(coverage);
+         redraw();
+      }
+   }
+
+   /* (non-Javadoc)
+    * @see org.eclipse.swt.events.MouseTrackListener#mouseHover(org.eclipse.swt.events.MouseEvent)
+    */
+   @Override
+   public void mouseHover(MouseEvent e)
+   {
+      if (objectTooltipRectangle == null) // ignore hover if tooltip already open
+      {
+         AbstractObject object = getObjectAtPoint(new Point(e.x, e.y));
+         if (object != currentObject)
+         {
+            objectToolTipLocation = (object != null) ? new Point(e.x, e.y) : null;
+            setCurrentObject(object);
+         }
+      }
+   }
+
+   /* (non-Javadoc)
+    * @see org.eclipse.swt.events.MouseTrackListener#mouseEnter(org.eclipse.swt.events.MouseEvent)
+    */
+   @Override
+   public void mouseEnter(MouseEvent e)
+   {
+   }
+
+   /* (non-Javadoc)
+    * @see org.eclipse.swt.events.MouseTrackListener#mouseExit(org.eclipse.swt.events.MouseEvent)
+    */
+   @Override
+   public void mouseExit(MouseEvent e)
+   {
+      setCurrentObject(null);
+   }
+   
+   /* (non-Javadoc)
+    * @see org.netxms.ui.eclipse.osm.widgets.AbstractGeoMapViewer#mouseMove(org.eclipse.swt.events.MouseEvent)
+    */
+   @Override
+   public void mouseMove(MouseEvent e)
+   {
+      super.mouseMove(e);
+      if ((objectTooltipRectangle != null) && !objectTooltipRectangle.contains(e.x, e.y))
+      {
+         objectTooltipRectangle = null;
+         objectToolTipLocation = null;
+         currentObject = null;
+         redraw();
+      }
+   }
+
+   /* (non-Javadoc)
+    * @see org.netxms.ui.eclipse.osm.widgets.AbstractGeoMapViewer#mouseDown(org.eclipse.swt.events.MouseEvent)
+    */
+   @Override
+   public void mouseDown(MouseEvent e)
+   {
+      super.mouseDown(e);
+      if (e.button != 1)
+      {
+         AbstractObject object = getObjectAtPoint(currentPoint);
+         if (object != currentObject)
+            setCurrentObject(object);
+      }
+   }
+
+   /**
+    * Set current object
+    * 
+    * @param object
+    */
+   private void setCurrentObject(AbstractObject object)
+   {
+      currentObject = object;
+      if (currentObject != null)
+      {
+         int idx = objects.indexOf(currentObject);
+         objects.remove(idx);
+         objects.add(currentObject);
+      }
+      redraw();
+   }
+
+   /* (non-Javadoc)
+    * @see org.netxms.ui.eclipse.osm.widgets.AbstractGeoMapViewer#drawContent(org.eclipse.swt.graphics.GC, org.netxms.base.GeoLocation, int, int)
+    */
+   @Override
+   protected void drawContent(GC gc, GeoLocation currentLocation, int imgW, int imgH)
+   {
+      objectIcons.clear();
+      
+      final Point centerXY = GeoLocationCache.coordinateToDisplay(currentLocation, accessor.getZoom());
+      for(AbstractObject object : objects)
+      {
+         final Point virtualXY = GeoLocationCache.coordinateToDisplay(object.getGeolocation(), accessor.getZoom());
+         final int dx = virtualXY.x - centerXY.x;
+         final int dy = virtualXY.y - centerXY.y;
+         drawObject(gc, imgW / 2 + dx, imgH / 2 + dy, object);
+      }
+      if (objectToolTipLocation != null)
+         drawObjectToolTip(gc);
+   }
+
+   /**
+    * Draw object on map
+    * 
+    * @param gc
+    * @param x
+    * @param y
+    * @param object
+    */
+   private void drawObject(GC gc, int x, int y, AbstractObject object) 
+   {
+      boolean selected = (currentObject != null) && (currentObject.getObjectId() == object.getObjectId());
+      
+      Image image = labelProvider.getImage(object);
+      if (image == null)
+         image = SharedIcons.IMG_UNKNOWN_OBJECT;
+      
+      int w = image.getImageData().width + LABEL_X_MARGIN * 2;
+      int h = image.getImageData().height+ LABEL_Y_MARGIN * 2;
+      Rectangle rect = new Rectangle(x - w / 2 - 1, y - LABEL_ARROW_HEIGHT - h, w, h);
+      
+      Color bgColor = ColorConverter.adjustColor(StatusDisplayInfo.getStatusColor(object.getStatus()), new RGB(255, 255, 255),  0.5f, colorCache);
+      
+      gc.setBackground(bgColor);
+      gc.fillRoundRectangle(rect.x, rect.y, rect.width, rect.height, 4, 4);
+      if (selected)
+      {
+         gc.setLineWidth(3);
+         gc.setForeground(ACTIVE_BORDER_COLOR);
+         gc.drawRoundRectangle(rect.x, rect.y, rect.width, rect.height, 4, 4);
+      }
+      gc.setLineWidth(1);
+      gc.setForeground(BORDER_COLOR);
+      gc.drawRoundRectangle(rect.x, rect.y, rect.width, rect.height, 4, 4);
+      
+      final int[] arrow = new int[] { x - 4, rect.y + rect.height, x, y, x + 4, rect.y + rect.height };
+
+      gc.fillPolygon(arrow);
+      gc.setForeground(bgColor);
+      gc.drawPolygon(arrow);
+      if (selected)
+      {
+         gc.drawLine(arrow[0], arrow[1] - 1, arrow[4], arrow[5] - 1);
+         gc.setLineWidth(3);
+         gc.setForeground(ACTIVE_BORDER_COLOR);
+         gc.drawPolyline(arrow);
+      }
+      gc.setLineWidth(1);
+      gc.setForeground(BORDER_COLOR);
+      gc.drawPolyline(arrow);
+
+      gc.drawImage(image, rect.x + LABEL_X_MARGIN, rect.y + LABEL_Y_MARGIN);
+      
+      objectIcons.add(new ObjectIcon(object, rect, x, y));
+   }
+   
+   /**
+    * Draw tooltip for current object
+    * 
+    * @param gc
+    */
+   private void drawObjectToolTip(GC gc)
+   {
+      gc.setFont(objectToolTipHeaderFont);
+      Point titleSize = gc.textExtent(currentObject.getObjectName());
+      gc.setFont(JFaceResources.getDefaultFont());
+      
+      // Calculate width and height
+      int width = Math.max(titleSize.x + 12, 128);
+      int height = OBJECT_TOOLTIP_Y_MARGIN * 2 + titleSize.y + 2 + OBJECT_TOOLTIP_SPACING;
+      
+      final String location = currentObject.getGeolocation().toString();
+      Point pt = gc.textExtent(location);
+      if (width < pt.x)
+         width = pt.x;
+      height += pt.y;
+      
+      final String postalAddress = currentObject.getPostalAddress().getAddressLine();
+      if (!postalAddress.isEmpty())
+      {
+         pt = gc.textExtent(postalAddress);
+         if (width < pt.x)
+            width = pt.x;
+         height += pt.y + OBJECT_TOOLTIP_SPACING;
+      }
+      
+      if ((currentObject.getComments() != null) && !currentObject.getComments().isEmpty())
+      {
+         pt = gc.textExtent(currentObject.getComments());
+         if (width < pt.x)
+            width = pt.x;
+         height += pt.y + OBJECT_TOOLTIP_SPACING * 2 + 1;
+      }
+      
+      width += OBJECT_TOOLTIP_X_MARGIN * 2;
+      
+      Rectangle ca = getClientArea();
+      Rectangle rect = new Rectangle(objectToolTipLocation.x - width / 2, objectToolTipLocation.y - height / 2, width, height);
+      if (rect.x < 0)
+         rect.x = 0;
+      else if (rect.x + rect.width >= ca.width)
+         rect.x = ca.width - rect.width - 1;
+      if (rect.y < 0)
+         rect.y = 0;
+      else if (rect.y + rect.height  >= ca.height)
+         rect.y = ca.height - rect.height - 1;
+      
+      gc.setBackground(colorCache.create(224, 224, 224));
+      gc.setAlpha(192);
+      gc.fillRoundRectangle(rect.x, rect.y, rect.width, rect.height, 3, 3);
+      
+      gc.setForeground(colorCache.create(92, 92, 92));
+      gc.setAlpha(255);
+      gc.setLineWidth(3);
+      gc.drawRoundRectangle(rect.x, rect.y, rect.width, rect.height, 3, 3);
+      gc.setLineWidth(1);
+      int y = rect.y + OBJECT_TOOLTIP_Y_MARGIN + titleSize.y + 2;
+      gc.drawLine(rect.x + 1, y, rect.x + rect.width - 1, y);
+      
+      gc.setBackground(StatusDisplayInfo.getStatusColor(currentObject.getStatus()));
+      gc.fillOval(rect.x + OBJECT_TOOLTIP_X_MARGIN, rect.y + OBJECT_TOOLTIP_Y_MARGIN + titleSize.y / 2 - 4, 8, 8);
+      
+      gc.setForeground(colorCache.create(0, 0, 0));
+      gc.setFont(objectToolTipHeaderFont);
+      gc.drawText(currentObject.getObjectName(), rect.x + OBJECT_TOOLTIP_X_MARGIN + 12, rect.y + OBJECT_TOOLTIP_Y_MARGIN, true);
+      
+      gc.setFont(JFaceResources.getDefaultFont());
+      int textLineHeight = gc.textExtent("M").y;
+      y = rect.y + OBJECT_TOOLTIP_Y_MARGIN + titleSize.y + OBJECT_TOOLTIP_SPACING + 2;
+      gc.drawText(location, rect.x + OBJECT_TOOLTIP_X_MARGIN, y, true);
+      if (!postalAddress.isEmpty())
+      {
+         y += textLineHeight;
+         gc.drawText(postalAddress, rect.x + OBJECT_TOOLTIP_X_MARGIN, y, true);
+      }
+      
+      if ((currentObject.getComments() != null) && !currentObject.getComments().isEmpty())
+      {
+         y += textLineHeight + OBJECT_TOOLTIP_SPACING;
+         gc.setForeground(colorCache.create(92, 92, 92));
+         gc.drawLine(rect.x + 1, y, rect.x + rect.width - 1, y);
+         y += OBJECT_TOOLTIP_SPACING;
+         gc.setForeground(colorCache.create(0, 0, 0));
+         gc.drawText(currentObject.getComments(), rect.x + OBJECT_TOOLTIP_X_MARGIN, y, true);
+      }
+      
+      objectTooltipRectangle = rect;
+   }
+
+   /* (non-Javadoc)
+    * @see org.netxms.ui.eclipse.osm.widgets.AbstractGeoMapViewer#getObjectAtPoint(org.eclipse.swt.graphics.Point)
+    */
+   @Override
+   public AbstractObject getObjectAtPoint(Point p)
+   {
+      for(int i = objectIcons.size() - 1; i >= 0; i--)
+      {
+         ObjectIcon icon = objectIcons.get(i);
+         if (icon.contains(p))
+            return icon.object;
+      }
+      return null;
+   }
+
+   /**
+    * Object icon on map
+    */
+   private class ObjectIcon
+   {
+      public Rectangle rect;
+      public Polygon arrow;
+      public AbstractObject object;
+
+      public ObjectIcon(AbstractObject object, Rectangle rect, int x, int y)
+      {
+         this.rect = rect;
+         this.object = object;
+         arrow = new Polygon();
+         arrow.addPoint(x, y);
+         arrow.addPoint(x - 4, rect.y + rect.height);
+         arrow.addPoint(x + 4, rect.y + rect.height);
+      }
+      
+      public boolean contains(Point p)
+      {
+         return rect.contains(p) || arrow.contains(p.x, p.y); 
+      }
+   }
+}
index eac9441..7392456 100644 (file)
@@ -23,7 +23,7 @@ import org.eclipse.swt.layout.FillLayout;
 import org.eclipse.ui.IViewPart;
 import org.netxms.client.dashboards.DashboardElement;
 import org.netxms.ui.eclipse.dashboard.widgets.internal.GeoMapConfig;
-import org.netxms.ui.eclipse.osm.widgets.GeoMapViewer;
+import org.netxms.ui.eclipse.osm.widgets.ObjectGeoLocationViewer;
 
 /**
  * Geo map element for dashboard
@@ -31,7 +31,7 @@ import org.netxms.ui.eclipse.osm.widgets.GeoMapViewer;
  */
 public class GeoMapElement extends ElementWidget
 {
-       private GeoMapViewer mapWidget;
+       private ObjectGeoLocationViewer mapWidget;
        private GeoMapConfig config;
        
        /**
@@ -57,7 +57,7 @@ public class GeoMapElement extends ElementWidget
                layout.marginWidth = 0;
                setLayout(layout);
                
-               mapWidget = new GeoMapViewer(this, SWT.NONE, false, null);
+               mapWidget = new ObjectGeoLocationViewer(this, SWT.NONE);
                mapWidget.setViewPart(viewPart);
                mapWidget.setTitle(config.getTitle());
                mapWidget.showMap(config.getLatitude(), config.getLongitude(), config.getZoom());
index 8500402..8202021 100644 (file)
@@ -343,7 +343,7 @@ public abstract class AbstractNetworkMapView extends ViewPart implements ISelect
                                        }
                                }
 
-                               // Default behaviour
+                               // Default behavior
                                actionOpenSubmap.run();
                        }
                });
index c6d98ef..59a7a04 100644 (file)
@@ -44,7 +44,7 @@ import org.netxms.ui.eclipse.console.resources.SharedIcons;
 import org.netxms.ui.eclipse.objectbrowser.api.ObjectContextMenu;
 import org.netxms.ui.eclipse.osm.Messages;
 import org.netxms.ui.eclipse.osm.tools.MapAccessor;
-import org.netxms.ui.eclipse.osm.widgets.GeoMapViewer;
+import org.netxms.ui.eclipse.osm.widgets.AbstractGeoMapViewer;
 import org.netxms.ui.eclipse.osm.widgets.helpers.GeoMapListener;
 import org.netxms.ui.eclipse.shared.ConsoleSharedData;
 
@@ -55,7 +55,7 @@ public abstract class AbstractGeolocationView extends ViewPart implements ISelec
 {
        public static final String JOB_FAMILY = "MapViewJob"; //$NON-NLS-1$
        
-       protected GeoMapViewer map;
+       protected AbstractGeoMapViewer map;
        
        private MapAccessor mapAccessor;
        private int zoomLevel = 15;
@@ -103,7 +103,7 @@ public abstract class AbstractGeolocationView extends ViewPart implements ISelec
        public void createPartControl(Composite parent)
        {
                // Map control
-               map = new GeoMapViewer(parent, SWT.BORDER, false, null);
+               map = createMapViewer(parent, SWT.BORDER);
                map.setViewPart(this);
                
                createActions();
@@ -138,6 +138,15 @@ public abstract class AbstractGeolocationView extends ViewPart implements ISelec
        }
 
        /**
+        * Create actual map viewer control
+        * 
+        * @param parent
+        * @param style
+        * @return
+        */
+       protected abstract AbstractGeoMapViewer createMapViewer(Composite parent, int style);
+       
+       /**
         * Create actions
         */
        protected void createActions()
index 40d94e9..303da5a 100644 (file)
@@ -41,7 +41,7 @@ import org.netxms.ui.eclipse.console.resources.SharedIcons;
 import org.netxms.ui.eclipse.osm.Messages;
 import org.netxms.ui.eclipse.osm.dialogs.TimeSelectionDialog;
 import org.netxms.ui.eclipse.osm.tools.MapAccessor;
-import org.netxms.ui.eclipse.osm.widgets.GeoMapViewer;
+import org.netxms.ui.eclipse.osm.widgets.GeoLocationHistoryViewer;
 import org.netxms.ui.eclipse.osm.widgets.helpers.GeoMapListener;
 import org.netxms.ui.eclipse.shared.ConsoleSharedData;
 
@@ -62,7 +62,7 @@ public class HistoryView extends ViewPart
         Messages.get().HistoryView_Preset2days, Messages.get().HistoryView_Preset5days, Messages.get().HistoryView_Preset1week, Messages.get().HistoryView_Preset1month,Messages.get().HistoryView_Preset1year };
 
        
-       protected GeoMapViewer map;
+       protected GeoLocationHistoryViewer map;
        
        private MapAccessor mapAccessor;
        private int zoomLevel = 15;
@@ -130,7 +130,7 @@ public class HistoryView extends ViewPart
        public void createPartControl(Composite parent)
        {
                // Map control
-               map = new GeoMapViewer(parent, SWT.BORDER, true, object);
+               map = new GeoLocationHistoryViewer(parent, SWT.BORDER, object);
                map.setViewPart(this);
                
                createActions(parent);
index 97a4c21..64f439f 100644 (file)
  */
 package org.netxms.ui.eclipse.osm.views;
 
+import org.eclipse.swt.widgets.Composite;
 import org.eclipse.ui.IViewSite;
 import org.eclipse.ui.PartInitException;
 import org.netxms.base.GeoLocation;
 import org.netxms.client.NXCSession;
 import org.netxms.client.objects.AbstractObject;
 import org.netxms.ui.eclipse.osm.Messages;
+import org.netxms.ui.eclipse.osm.widgets.AbstractGeoMapViewer;
+import org.netxms.ui.eclipse.osm.widgets.ObjectGeoLocationViewer;
 import org.netxms.ui.eclipse.shared.ConsoleSharedData;
 
 /**
@@ -58,6 +61,15 @@ public class LocationMap extends AbstractGeolocationView
        }
 
        /* (non-Javadoc)
+    * @see org.netxms.ui.eclipse.osm.views.AbstractGeolocationView#createMapViewer(org.eclipse.swt.widgets.Composite, int)
+    */
+   @Override
+   protected AbstractGeoMapViewer createMapViewer(Composite parent, int style)
+   {
+      return new ObjectGeoLocationViewer(parent, style);
+   }
+
+   /* (non-Javadoc)
         * @see org.netxms.ui.eclipse.osm.views.AbstractGeolocationView#getInitialCenterPoint()
         */
        @Override
index 7c1e9ac..3c3b358 100644 (file)
@@ -1,6 +1,6 @@
 /**
  * NetXMS - open source network management system
- * Copyright (C) 2003-2013 Victor Kirhenshtein
+ * Copyright (C) 2003-2015 Victor Kirhenshtein
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -23,6 +23,7 @@ import org.eclipse.jface.action.Action;
 import org.eclipse.jface.action.IMenuManager;
 import org.eclipse.jface.action.Separator;
 import org.eclipse.jface.window.Window;
+import org.eclipse.swt.widgets.Composite;
 import org.eclipse.ui.IMemento;
 import org.eclipse.ui.IViewSite;
 import org.eclipse.ui.PartInitException;
@@ -34,6 +35,8 @@ import org.netxms.ui.eclipse.objectbrowser.dialogs.ObjectSelectionDialog;
 import org.netxms.ui.eclipse.osm.Activator;
 import org.netxms.ui.eclipse.osm.Messages;
 import org.netxms.ui.eclipse.osm.tools.MapAccessor;
+import org.netxms.ui.eclipse.osm.widgets.AbstractGeoMapViewer;
+import org.netxms.ui.eclipse.osm.widgets.ObjectGeoLocationViewer;
 import org.netxms.ui.eclipse.shared.ConsoleSharedData;
 
 /**
@@ -66,6 +69,15 @@ public class WorldMap extends AbstractGeolocationView
                super.init(site, memento);
        }
 
+   /* (non-Javadoc)
+    * @see org.netxms.ui.eclipse.osm.views.AbstractGeolocationView#createMapViewer(org.eclipse.swt.widgets.Composite, int)
+    */
+   @Override
+   protected AbstractGeoMapViewer createMapViewer(Composite parent, int style)
+   {
+      return new ObjectGeoLocationViewer(parent, style);
+   }
+
        /* (non-Javadoc)
         * @see org.eclipse.ui.part.ViewPart#saveState(org.eclipse.ui.IMemento)
         */
diff --git a/webui/webapp/OSM/src/org/netxms/ui/eclipse/osm/widgets/AbstractGeoMapViewer.java b/webui/webapp/OSM/src/org/netxms/ui/eclipse/osm/widgets/AbstractGeoMapViewer.java
new file mode 100644 (file)
index 0000000..ed279dd
--- /dev/null
@@ -0,0 +1,852 @@
+/**
+ * NetXMS - open source network management system
+ * Copyright (C) 2003-2015 Victor Kirhenshtein
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package org.netxms.ui.eclipse.osm.widgets;
+
+import java.util.HashSet;
+import java.util.Set;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.jface.resource.JFaceResources;
+import org.eclipse.jface.viewers.ILabelProvider;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.dnd.DND;
+import org.eclipse.swt.dnd.DragSource;
+import org.eclipse.swt.dnd.DragSourceEvent;
+import org.eclipse.swt.dnd.DragSourceListener;
+import org.eclipse.swt.events.DisposeEvent;
+import org.eclipse.swt.events.DisposeListener;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.MouseListener;
+import org.eclipse.swt.events.MouseMoveListener;
+import org.eclipse.swt.events.PaintEvent;
+import org.eclipse.swt.events.PaintListener;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Canvas;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.Widget;
+import org.eclipse.ui.IViewPart;
+import org.eclipse.ui.model.WorkbenchLabelProvider;
+import org.eclipse.ui.presentations.PresentationUtil;
+import org.netxms.base.GeoLocation;
+import org.netxms.client.objects.AbstractObject;
+import org.netxms.ui.eclipse.console.resources.SharedColors;
+import org.netxms.ui.eclipse.jobs.ConsoleJob;
+import org.netxms.ui.eclipse.osm.Activator;
+import org.netxms.ui.eclipse.osm.GeoLocationCache;
+import org.netxms.ui.eclipse.osm.GeoLocationCacheListener;
+import org.netxms.ui.eclipse.osm.Messages;
+import org.netxms.ui.eclipse.osm.tools.Area;
+import org.netxms.ui.eclipse.osm.tools.MapAccessor;
+import org.netxms.ui.eclipse.osm.tools.MapLoader;
+import org.netxms.ui.eclipse.osm.tools.Tile;
+import org.netxms.ui.eclipse.osm.tools.TileSet;
+import org.netxms.ui.eclipse.osm.widgets.helpers.GeoMapListener;
+import org.netxms.ui.eclipse.tools.ColorCache;
+import org.netxms.ui.eclipse.tools.FontTools;
+
+/**
+ * This widget shows map retrieved via OpenStreetMap Static Map API
+ */
+public abstract class AbstractGeoMapViewer extends Canvas implements PaintListener, GeoLocationCacheListener, MouseListener, MouseMoveListener
+{
+   protected static final String[] TITLE_FONTS = { "Segoe UI", "Liberation Sans", "DejaVu Sans", "Verdana", "Arial" };
+   
+   protected static final Color BORDER_COLOR = new Color(Display.getCurrent(), 0, 0, 0);
+   
+       protected static final int LABEL_ARROW_HEIGHT = 5;
+       protected static final int LABEL_X_MARGIN = 4;
+       protected static final int LABEL_Y_MARGIN = 4;
+       protected static final int LABEL_SPACING = 4;
+
+   private static final Color MAP_BACKGROUND = new Color(Display.getCurrent(), 255, 255, 255);
+   private static final Color INFO_BLOCK_BACKGROUND = new Color(Display.getCurrent(), 0, 0, 0);
+   private static final Color INFO_BLOCK_TEXT = new Color(Display.getCurrent(), 255, 255, 255);
+   private static final Color SELECTION_COLOR = new Color(Display.getCurrent(), 0, 0, 255);
+   
+       private static final int DRAG_JITTER = 8;
+
+   protected ColorCache colorCache;
+       protected ILabelProvider labelProvider;
+   protected Area coverage = new Area(0, 0, 0, 0);
+   protected MapAccessor accessor;
+   protected IViewPart viewPart = null;
+   protected Point currentPoint;
+
+       private MapLoader mapLoader;
+       private Point dragStartPoint = null;
+       private Point selectionStartPoint = null;
+       private Point selectionEndPoint = null;
+       private Set<GeoMapListener> mapListeners = new HashSet<GeoMapListener>(0);
+       private String title = null;
+       private int offsetX;
+       private int offsetY;
+       private TileSet currentTileSet = null;
+       private Image imageZoomIn;
+       private Image imageZoomOut;
+       private Rectangle zoomControlRect = null;
+   private Font mapTitleFont;
+   private RAPDragTracker tracker;
+       
+       /**
+        * @param parent
+        * @param style
+        */
+       public AbstractGeoMapViewer(Composite parent, int style)
+       {
+               super(parent, style | SWT.NO_BACKGROUND | SWT.DOUBLE_BUFFERED);
+               
+               colorCache = new ColorCache(this);
+               
+               imageZoomIn = Activator.getImageDescriptor("icons/map_zoom_in.png").createImage(); //$NON-NLS-1$
+      imageZoomOut = Activator.getImageDescriptor("icons/map_zoom_out.png").createImage(); //$NON-NLS-1$
+      
+      mapTitleFont = FontTools.createFont(TITLE_FONTS, 2, SWT.BOLD);
+
+               labelProvider = WorkbenchLabelProvider.getDecoratingWorkbenchLabelProvider();
+               mapLoader = new MapLoader(getDisplay());
+
+               setBackground(MAP_BACKGROUND);
+               addPaintListener(this);
+
+               final Runnable timer = new Runnable() {
+                       @Override
+                       public void run()
+                       {
+                               if (isDisposed())
+                                       return;
+
+                               reloadMap();
+                       }
+               };
+
+               addListener(SWT.Resize, new Listener() {
+                       @Override
+                       public void handleEvent(Event event)
+                       {
+                               getDisplay().timerExec(-1, timer);
+                               getDisplay().timerExec(1000, timer);
+                       }
+               });
+
+               addDisposeListener(new DisposeListener() {
+                       @Override
+                       public void widgetDisposed(DisposeEvent e)
+                       {
+                               labelProvider.dispose();
+                               GeoLocationCache.getInstance().removeListener(AbstractGeoMapViewer.this);
+                               mapLoader.dispose();
+                               imageZoomIn.dispose();
+                               imageZoomOut.dispose();
+                               mapTitleFont.dispose();
+                       }
+               });
+
+               addMouseListener(this);
+
+      /* the following code is a hack for adding
+       * mouse drag support in RAP. Copied from draw2d
+       * with slight modifications
+       */
+      PresentationUtil.addDragListener(this, new Listener() {
+         @Override
+         public void handleEvent(Event event)
+         {
+            if (event.type == SWT.DragDetect) 
+            {
+               MouseEvent me = new MouseEvent(event);
+               me.stateMask = SWT.BUTTON1;
+               AbstractGeoMapViewer.this.mouseDown(me);
+            }        
+         }
+      });
+      DragSource dragSource = new DragSource(this, DND.DROP_MOVE | DND.DROP_COPY | DND.DROP_LINK);
+      final DragSourceListener listener = new DragSourceListener() {
+         private static final long serialVersionUID = 1L;
+
+         public void dragStart(DragSourceEvent event)
+         {
+            tracker = new RAPDragTracker(AbstractGeoMapViewer.this, AbstractGeoMapViewer.this);
+            tracker.open();
+         }
+
+         public void dragSetData(DragSourceEvent event)
+         {
+         }
+
+         public void dragFinished(DragSourceEvent event)
+         {
+            if (tracker != null)
+            {
+               tracker.close();
+               tracker = null;
+            }
+         }
+      };
+      dragSource.addDragListener(listener);
+      /* end of mouse drag hack */
+
+      GeoLocationCache.getInstance().addListener(this);
+       }
+               
+       /**
+        * Add zoom level change listener
+        * 
+        * @param listener
+        */
+       public void addMapListener(GeoMapListener listener)
+       {
+               mapListeners.add(listener);
+       }
+       
+       /**
+        * Remove previously registered zoom change listener
+        * 
+        * @param listener
+        */
+       public void removeMapListener(GeoMapListener listener)
+       {
+               mapListeners.remove(listener);
+       }
+       
+       /**
+        * Notify all listeners about zoom level change
+        */
+       private void notifyOnZoomChange()
+       {
+               for(GeoMapListener listener : mapListeners)
+                       listener.onZoom(accessor.getZoom());
+       }
+
+       /**
+        * Notify all listeners about position change
+        */
+       private void notifyOnPositionChange()
+       {
+               for(GeoMapListener listener : mapListeners)
+                       listener.onPan(accessor.getCenterPoint());
+       }
+
+       /**
+        * Show given map
+        * 
+        * @param accessor
+        */
+       public void showMap(MapAccessor accessor)
+       {
+               this.accessor = new MapAccessor(accessor);
+               reloadMap();
+       }
+
+       /**
+        * @param lat
+        * @param lon
+        * @param zoom
+        */
+       public void showMap(double lat, double lon, int zoom)
+       {
+               showMap(new MapAccessor(lat, lon, zoom));
+       }
+
+       /**
+        * Reload current map
+        */
+       private void reloadMap()
+       {
+               Rectangle rect = this.getClientArea();
+               accessor.setMapWidth(rect.width);
+               accessor.setMapHeight(rect.height);
+
+               if (!accessor.isValid())
+                       return;
+
+               final Point mapSize = new Point(accessor.getMapWidth(), accessor.getMapHeight());
+               final GeoLocation centerPoint = accessor.getCenterPoint();
+               ConsoleJob job = new ConsoleJob(Messages.get().GeoMapViewer_DownloadJob_Title, viewPart, Activator.PLUGIN_ID, null) {
+                       @Override
+                       protected void runInternal(IProgressMonitor monitor) throws Exception
+                       {
+                               final TileSet tiles = mapLoader.getAllTiles(mapSize, centerPoint, MapLoader.CENTER, accessor.getZoom(), true);
+                               runInUIThread(new Runnable() {
+                                       @Override
+                                       public void run()
+                                       {
+                  if (isDisposed())
+                     return;
+                  
+                  if (currentTileSet != null)
+                     currentTileSet.dispose();
+                  currentTileSet = tiles;
+                  if ((tiles != null) && (tiles.missingTiles > 0))
+                     loadMissingTiles(tiles);
+                  
+                  Rectangle clientArea = getClientArea();
+                  Point mapSize = new Point(clientArea.width, clientArea.height);
+                  coverage = GeoLocationCache.calculateCoverage(mapSize, accessor.getCenterPoint(), GeoLocationCache.CENTER, accessor.getZoom());
+                  onMapLoad();
+                                       }
+                               });
+                       }
+
+                       @Override
+                       protected String getErrorMessage()
+                       {
+                               return Messages.get().GeoMapViewer_DownloadError;
+                       }
+               };
+               job.setUser(false);
+               job.start();
+       }
+       
+       /**
+        * Map load handler. Called on UI thread after map was (re)loaded.
+        */
+       protected abstract void onMapLoad();
+       
+       /**
+        * Load missing tiles in tile set
+        * 
+        * @param tiles
+        */
+       private void loadMissingTiles(final TileSet tiles)
+       {
+               ConsoleJob job = new ConsoleJob(Messages.get().GeoMapViewer_LoadMissingJob_Title, viewPart, Activator.PLUGIN_ID, null) {
+                       @Override
+                       protected void runInternal(IProgressMonitor monitor) throws Exception
+                       {
+                               mapLoader.loadMissingTiles(tiles, new Runnable() {
+                                       @Override
+                                       public void run()
+                                       {
+                                               if (!AbstractGeoMapViewer.this.isDisposed() && (currentTileSet == tiles))
+                                               {
+                                                       AbstractGeoMapViewer.this.redraw();
+                                               }
+                                               tiles.dispose();
+                                       }
+                               });
+                       }
+                       
+                       @Override
+                       protected String getErrorMessage()
+                       {
+                               return Messages.get().GeoMapViewer_DownloadError;
+                       }
+               };
+               job.setUser(false);
+               job.start();
+       }
+
+       /**
+        * @param tiles
+        */
+       private void drawTiles(GC gc, TileSet tileSet)
+       {
+      final Tile[][] tiles = tileSet.tiles;
+
+      Point size = getSize();
+
+      int x = tileSet.xOffset;
+      int y = tileSet.yOffset;
+      for(int i = 0; i < tiles.length; i++)
+      {
+         for(int j = 0; j < tiles[i].length; j++)
+         {
+            gc.drawImage(tiles[i][j].getImage(), x, y);
+            x += 256;
+            if (x >= size.x)
+            {
+               x = tileSet.xOffset;
+               y += 256;
+            }
+         }
+      }
+       }
+
+       /*
+        * (non-Javadoc)
+        * @see org.eclipse.swt.events.PaintListener#paintControl(org.eclipse.swt.events.PaintEvent)
+        */
+       @Override
+       public void paintControl(PaintEvent e)
+       {
+      final GC gc = e.gc;
+      gc.setAntialias(SWT.ON);
+      gc.setTextAntialias(SWT.ON);
+      
+      if (currentTileSet != null)
+         drawTiles(gc, currentTileSet);
+      
+               GeoLocation currentLocation;
+
+               // Draw objects and decorations if user is not dragging map
+               // and map is not currently loading
+               if (dragStartPoint == null)
+               {
+         currentLocation = accessor.getCenterPoint();
+         Rectangle rect = getClientArea();
+             drawContent(gc, currentLocation, rect.width, rect.height);
+               }
+               else
+               {
+                  Point cp = GeoLocationCache.coordinateToDisplay(accessor.getCenterPoint(), accessor.getZoom());
+                  cp.x += offsetX;
+                  cp.y += offsetY;
+                  currentLocation = GeoLocationCache.displayToCoordinates(cp, accessor.getZoom());
+                  
+             Point size = getSize();
+                  TileSet tileSet = mapLoader.getAllTiles(size, currentLocation, MapLoader.CENTER, accessor.getZoom(), true);
+             int x = tileSet.xOffset;
+             int y = tileSet.yOffset;
+             final Tile[][] tiles = tileSet.tiles;
+             for(int i = 0; i < tiles.length; i++)
+             {
+                for(int j = 0; j < tiles[i].length; j++)
+                {
+                   gc.drawImage(tiles[i][j].getImage(), x, y);
+                   x += 256;
+                   if (x >= size.x)
+                   {
+                      x = tileSet.xOffset;
+                      y += 256;
+                   }
+                }
+             }
+             tileSet.dispose();
+               }
+               
+               // Draw selection rectangle
+               if ((selectionStartPoint != null) && (selectionEndPoint != null))
+               {
+                       int x = Math.min(selectionStartPoint.x, selectionEndPoint.x);
+                       int y = Math.min(selectionStartPoint.y, selectionEndPoint.y);
+                       int w = Math.abs(selectionStartPoint.x - selectionEndPoint.x);
+                       int h = Math.abs(selectionStartPoint.y - selectionEndPoint.y);
+                       gc.setBackground(SELECTION_COLOR);
+                       gc.setForeground(SELECTION_COLOR);
+                       gc.setAlpha(64);
+                       gc.fillRectangle(x, y, w, h);
+                       gc.setAlpha(255);
+                       gc.setLineWidth(2);
+                       gc.drawRectangle(x, y, w, h);
+               }
+               
+               // Draw current location info
+      String text = currentLocation.toString();
+      Point textSize = gc.textExtent(text);
+
+      Rectangle rect = getClientArea();
+      rect.x = rect.width - textSize.x - 20;
+      rect.y += 10;
+      rect.width = textSize.x + 10;
+      rect.height = textSize.y + 8;
+
+      gc.setBackground(INFO_BLOCK_BACKGROUND);
+      gc.setAlpha(128);
+      gc.fillRoundRectangle(rect.x, rect.y, rect.width, rect.height, 8, 8);
+      gc.setAlpha(255);
+
+      gc.setForeground(INFO_BLOCK_TEXT);
+      gc.drawText(text, rect.x + 5, rect.y + 4, true);
+               
+               // Draw title
+               if ((title != null) && !title.isEmpty())
+               {
+                       gc.setFont(mapTitleFont);
+                       rect = getClientArea();
+                       int x = (rect.width - gc.textExtent(title).x) / 2;
+                       gc.setForeground(SharedColors.getColor(SharedColors.GEOMAP_TITLE, getDisplay()));
+                       gc.drawText(title, x, 10, true);
+               }
+               
+               // Draw zoom control
+               gc.setFont(JFaceResources.getHeaderFont());
+               text = Integer.toString(accessor.getZoom());
+               textSize = gc.textExtent(text);
+               
+      rect = getClientArea();
+      rect.x = 10;
+      rect.y = 10;
+      rect.width = 80;
+      rect.height = 47 + textSize.y;
+
+      gc.setBackground(INFO_BLOCK_BACKGROUND);
+      gc.setAlpha(128);
+      gc.fillRoundRectangle(rect.x, rect.y, rect.width, rect.height, 8, 8);
+      gc.setAlpha(255);
+      
+      gc.drawText(text, rect.x + rect.width / 2 - textSize.x / 2, rect.y + 5, true);
+      gc.drawImage(imageZoomIn, rect.x + 5, rect.y + rect.height - 37);
+      gc.drawImage(imageZoomOut, rect.x + 42, rect.y + rect.height - 37);
+      
+      zoomControlRect = rect;
+       }
+       
+       /**
+        * Draw content over map
+        * 
+        * @param gc
+        * @param currentLocation current location (map center)
+        * @param imgW map image width
+        * @param imgH map image height
+        */
+       protected abstract void drawContent(GC gc, GeoLocation currentLocation, int imgW, int imgH);
+
+       /*
+        * (non-Javadoc)
+        * 
+        * @see
+        * org.netxms.ui.eclipse.osm.GeoLocationCacheListener#geoLocationCacheChanged
+        * (org.netxms.client.objects.AbstractObject, org.netxms.client.GeoLocation)
+        */
+       @Override
+       public void geoLocationCacheChanged(final AbstractObject object, final GeoLocation prevLocation)
+       {
+               getDisplay().asyncExec(new Runnable() {
+                       @Override
+                       public void run()
+                       {
+                     onCacheChange(object, prevLocation);
+                       }
+               });
+       }
+
+       /**
+        * Real handler for geolocation cache changes. Must be run in UI thread.
+        * 
+        * @param object
+        * @param prevLocation
+        */
+       protected abstract void onCacheChange(final AbstractObject object, final GeoLocation prevLocation);
+       
+       /* (non-Javadoc)
+        * @see org.eclipse.swt.events.MouseListener#mouseDoubleClick(org.eclipse.swt.events.MouseEvent)
+        */
+       @Override
+       public void mouseDoubleClick(MouseEvent e)
+       {
+       }
+
+       /* (non-Javadoc)
+        * @see org.eclipse.swt.events.MouseListener#mouseDown(org.eclipse.swt.events.MouseEvent)
+        */
+       @Override
+       public void mouseDown(MouseEvent e)
+       {
+               if (e.button == 1) // left button, ignore if map is currently loading
+               {
+                  if (zoomControlRect.contains(e.x, e.y))
+                  {
+                     Rectangle r = new Rectangle(zoomControlRect.x + 5, zoomControlRect.y + zoomControlRect.height - 37, 32, 32);
+                     int zoom = accessor.getZoom();
+                     if (r.contains(e.x, e.y))
+                     {
+                        if (zoom < 18)
+                           zoom++;
+                     }
+                     else
+                     {
+                        r.x += 37;
+               if (r.contains(e.x, e.y))
+               {
+                  if (zoom > 1)
+                     zoom--;
+               }
+                     }
+                     
+                     if (zoom != accessor.getZoom())
+                     {
+                        accessor.setZoom(zoom);
+                        reloadMap();
+                        notifyOnZoomChange();
+                     }
+                  }
+                  else if ((e.stateMask & SWT.SHIFT) != 0)
+                       {
+                               if (accessor.getZoom() < 18)
+                                       selectionStartPoint = new Point(e.x, e.y);
+                       }
+                       else
+                       {
+                               dragStartPoint = new Point(e.x, e.y);
+                               setCursor(getDisplay().getSystemCursor(SWT.CURSOR_SIZEALL));
+                       }
+               }
+
+               currentPoint = new Point(e.x, e.y);
+       }
+
+       /* (non-Javadoc)
+        * @see org.eclipse.swt.events.MouseListener#mouseUp(org.eclipse.swt.events.MouseEvent)
+        */
+       @Override
+       public void mouseUp(MouseEvent e)
+       {
+               if ((e.button == 1) && (dragStartPoint != null))
+               {
+                       if (Math.abs(offsetX) > DRAG_JITTER || Math.abs(offsetY) > DRAG_JITTER)
+                       {
+                               final Point centerXY = GeoLocationCache.coordinateToDisplay(accessor.getCenterPoint(), accessor.getZoom());
+                               centerXY.x += offsetX;
+                               centerXY.y += offsetY;
+                               final GeoLocation geoLocation = GeoLocationCache.displayToCoordinates(centerXY, accessor.getZoom());
+                               accessor.setLatitude(geoLocation.getLatitude());
+                               accessor.setLongitude(geoLocation.getLongitude());
+                               reloadMap();
+                               notifyOnPositionChange();
+                       }
+                       offsetX = 0;
+                       offsetY = 0;
+                       dragStartPoint = null;
+                       setCursor(null);
+               }
+               if ((e.button == 1) && (selectionStartPoint != null))
+               {
+                       if (selectionEndPoint != null)
+                       {
+                               int x1 = Math.min(selectionStartPoint.x, selectionEndPoint.x);
+                               int x2 = Math.max(selectionStartPoint.x, selectionEndPoint.x);
+                               int y1 = Math.min(selectionStartPoint.y, selectionEndPoint.y);
+                               int y2 = Math.max(selectionStartPoint.y, selectionEndPoint.y);
+
+                               final GeoLocation l1 = getLocationAtPoint(new Point(x1, y1));
+                               final GeoLocation l2 = getLocationAtPoint(new Point(x2, y2));
+                               final GeoLocation lc = getLocationAtPoint(new Point(x2 - (x2 - x1) / 2, y2 - (y2 - y1) / 2));
+
+                               int zoom = accessor.getZoom();
+                               while(zoom < 18)
+                               {
+                                       zoom++;
+                                       final Area area = GeoLocationCache.calculateCoverage(getSize(), lc, GeoLocationCache.CENTER, zoom);
+                                       if (!area.contains(l1.getLatitude(), l1.getLongitude()) ||
+                                           !area.contains(l2.getLatitude(), l2.getLongitude()))
+                                       {
+                                               zoom--;
+                                               break;
+                                       }
+                               }
+
+                               if (zoom != accessor.getZoom())
+                               {
+                                       accessor.setZoom(zoom);
+                                       accessor.setLatitude(lc.getLatitude());
+                                       accessor.setLongitude(lc.getLongitude());
+                                       reloadMap();
+                                       notifyOnPositionChange();
+                                       notifyOnZoomChange();
+                               }
+                       }
+                       selectionStartPoint = null;
+                       selectionEndPoint = null;
+                       redraw();
+               }
+       }
+
+       /* (non-Javadoc)
+        * @see org.eclipse.swt.events.MouseMoveListener#mouseMove(org.eclipse.swt.events.MouseEvent)
+        */
+       @Override
+       public void mouseMove(MouseEvent e)
+       {
+               if (dragStartPoint != null)
+               {
+                       int deltaX = dragStartPoint.x - e.x;
+                       int deltaY = dragStartPoint.y - e.y;
+                       if (Math.abs(deltaX) > DRAG_JITTER || Math.abs(deltaY) > DRAG_JITTER)
+                       {
+                               offsetX = deltaX;
+                               offsetY = deltaY;
+                               redraw();
+                       }
+               }
+               if (selectionStartPoint != null)
+               {
+                       int deltaX = selectionStartPoint.x - e.x;
+                       int deltaY = selectionStartPoint.y - e.y;
+                       if (Math.abs(deltaX) > DRAG_JITTER || Math.abs(deltaY) > DRAG_JITTER)
+                       {
+                               selectionEndPoint = new Point(e.x, e.y);
+                               redraw();
+                       }
+               }
+       }
+
+       /**
+        * @return the currentPoint
+        */
+       public Point getCurrentPoint()
+       {
+               return new Point(currentPoint.x, currentPoint.y);
+       }
+       
+       /**
+        * Get location at given point within widget
+        * 
+        * @param p widget-related coordinates
+        * @return location (latitude/longitude) at given point
+        */
+       public GeoLocation getLocationAtPoint(Point p)
+       {
+               Point cp = GeoLocationCache.coordinateToDisplay(new GeoLocation(coverage.getxHigh(), coverage.getyLow()), accessor.getZoom());
+               return GeoLocationCache.displayToCoordinates(new Point(cp.x + p.x, cp.y + p.y), accessor.getZoom());
+       }
+       
+       /**
+        * @return the viewPart
+        */
+       public IViewPart getViewPart()
+       {
+               return viewPart;
+       }
+
+       /**
+        * @param viewPart the viewPart to set
+        */
+       public void setViewPart(IViewPart viewPart)
+       {
+               this.viewPart = viewPart;
+       }
+
+       /**
+        * @return the title
+        */
+       public String getTitle()
+       {
+               return title;
+       }
+
+       /**
+        * @param title the title to set
+        */
+       public void setTitle(String title)
+       {
+               this.title = title;
+       }
+
+   /**
+    * Get object at given point within widget
+    * 
+    * @param p widget-related coordinates
+    * @return object at given point or null
+    */
+   public abstract AbstractObject getObjectAtPoint(Point p);
+
+   /**
+    * Drag tracker - copied from draw2d
+    */
+   protected class RAPDragTracker
+   {
+      public boolean cancelled;
+      public boolean tracking;
+      private final MouseMoveListener listener;
+      private final Widget widget;
+
+      public RAPDragTracker(final MouseMoveListener listener, final Widget widget)
+      {
+         this.listener = listener;
+         this.widget = widget;
+      }
+
+      public void open()
+      {
+         Job dragJob = new Job("Drag-Job") {
+            protected IStatus run(IProgressMonitor monitor)
+            {
+               // Run tracker until mouse up occurs or escape key pressed.
+               final Display display = widget.getDisplay();
+               cancelled = false;
+               tracking = true;
+
+               try
+               {
+                  long timeout = 0;
+                  long refreshRate = 200;
+                  while(tracking && !cancelled)
+                  {
+                     if (!display.isDisposed())
+                     {
+                        display.syncExec(new Runnable()
+                        {
+                           public void run()
+                           {
+                              if (AbstractGeoMapViewer.this.isDisposed())
+                              {
+                                 tracking = false;
+                                 cancelled = true;
+                                 return;
+                              }
+                              Event ev = new Event();
+                              ev.display = display;
+                              Point loc = AbstractGeoMapViewer.this.toControl(display.getCursorLocation());
+                              ev.type = SWT.DragDetect;
+                              ev.widget = widget;
+                              ev.button = 1;
+                              ev.x = loc.x;
+                              ev.y = loc.y;
+                              MouseEvent me = new MouseEvent(ev);
+                              me.stateMask = SWT.BUTTON1;
+                              listener.mouseMove(me);
+                           }
+                        });
+                        timeout += refreshRate;
+                        if (timeout >= 60000)
+                        {
+                           cancelled = true;
+                        }
+                        Thread.sleep(refreshRate);
+                     }
+
+                  }
+               }
+               catch(InterruptedException e)
+               {
+                  e.printStackTrace();
+               }
+               finally
+               {
+                  display.syncExec(new Runnable()
+                  {
+                     public void run()
+                     {
+                        close();
+                     }
+                  });
+               }
+               return Status.OK_STATUS;
+            }
+         };
+         dragJob.setSystem(true);
+         dragJob.schedule();
+      }
+
+      public void close()
+      {
+         tracking = false;
+      }
+   }
+}
diff --git a/webui/webapp/OSM/src/org/netxms/ui/eclipse/osm/widgets/GeoLocationHistoryViewer.java b/webui/webapp/OSM/src/org/netxms/ui/eclipse/osm/widgets/GeoLocationHistoryViewer.java
new file mode 100644 (file)
index 0000000..fed3c17
--- /dev/null
@@ -0,0 +1,353 @@
+/**
+ * NetXMS - open source network management system
+ * Copyright (C) 2003-2015 Victor Kirhenshtein
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package org.netxms.ui.eclipse.osm.widgets;
+
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.ToolTip;
+import org.netxms.base.GeoLocation;
+import org.netxms.client.NXCSession;
+import org.netxms.client.TimePeriod;
+import org.netxms.client.datacollection.GraphSettings;
+import org.netxms.client.objects.AbstractObject;
+import org.netxms.ui.eclipse.console.resources.RegionalSettings;
+import org.netxms.ui.eclipse.jobs.ConsoleJob;
+import org.netxms.ui.eclipse.osm.Activator;
+import org.netxms.ui.eclipse.osm.GeoLocationCache;
+import org.netxms.ui.eclipse.osm.Messages;
+import org.netxms.ui.eclipse.osm.tools.Area;
+import org.netxms.ui.eclipse.osm.tools.QuadTree;
+import org.netxms.ui.eclipse.shared.ConsoleSharedData;
+
+/**
+ * Geo location viewer for object location history
+ */
+public class GeoLocationHistoryViewer extends AbstractGeoMapViewer
+{
+   private static final int START = 1;
+   private static final int END = 2;
+   private static final String pointInformation[] = { Messages.get().GeoMapViewer_Start, Messages.get().GeoMapViewer_End };
+   
+   private static final int LABEL_ARROW_OFFSET = 10;
+   
+   private static final Color LABEL_BACKGROUND = new Color(Display.getCurrent(), 240, 254, 192);
+   private static final Color LABEL_TEXT = new Color(Display.getCurrent(), 0, 0, 0);
+   private static final Color TRACK_COLOR = new Color(Display.getCurrent(), 163, 73, 164);
+   
+   private AbstractObject historyObject = null;
+   private List<GeoLocation> points = new ArrayList<GeoLocation>();
+   private TimePeriod timePeriod = new TimePeriod();
+   private ToolTip pointToolTip = null;
+   private QuadTree<GeoLocation> locationTree = new QuadTree<GeoLocation>();
+   private int selectedPoint = -1;
+
+   /**
+    * @param parent
+    * @param style
+    * @param object
+    */
+   public GeoLocationHistoryViewer(Composite parent, int style, AbstractObject object)
+   {
+      super(parent, style);
+      this.historyObject = object;
+      pointToolTip = new ToolTip(getShell(), SWT.BALLOON);
+   }
+
+   /*
+    * (non-Javadoc)
+    * 
+    * @see org.netxms.ui.eclipse.osm.widgets.AbstractGeoMapViewer#onMapLoad()
+    */
+   @Override
+   protected void onMapLoad()
+   {
+      updateHistory();
+   }
+
+   /* (non-Javadoc)
+    * @see org.netxms.ui.eclipse.osm.widgets.AbstractGeoMapViewer#onCacheChange(org.netxms.client.objects.AbstractObject, org.netxms.base.GeoLocation)
+    */
+   @Override
+   protected void onCacheChange(AbstractObject object, GeoLocation prevLocation)
+   {
+      if (object.getObjectId() == historyObject.getObjectId())
+         updateHistory();
+   }
+
+   /* (non-Javadoc)
+    * @see org.netxms.ui.eclipse.osm.widgets.AbstractGeoMapViewer#mouseMove(org.eclipse.swt.events.MouseEvent)
+    */
+   @Override
+   public void mouseMove(MouseEvent e)
+   {
+      super.mouseMove(e);
+      if (selectedPoint != -1)
+      {
+         selectedPoint = -1;
+         pointToolTip.setVisible(false);
+         redraw();
+      }
+   }
+
+   /**
+    * Get geolocations adjacent to given screen coordinates ordered by distance from that point
+    * 
+    * @param p
+    * @return
+    */
+   public List<GeoLocation> getAdjacentLocations(Point p)
+   {
+      return getAdjacentLocations(p.x, p.y);
+   }
+   
+   /**
+    * Get geolocations adjacent to given screen coordinates ordered by distance from that point
+    * 
+    * @param x
+    * @param y
+    * @return
+    */
+   public List<GeoLocation> getAdjacentLocations(int x, int y)
+   {
+      Point p = new Point(x, y);
+      final GeoLocation center = getLocationAtPoint(p);
+      
+      p.x -= 5;
+      p.y -= 5;
+      GeoLocation topLeft = getLocationAtPoint(p);
+      p.x += 10;
+      p.y += 10;
+      GeoLocation bottomRight = getLocationAtPoint(p);
+      Area area = new Area(topLeft.getLatitude(), topLeft.getLongitude(), bottomRight.getLatitude(), bottomRight.getLongitude());
+
+      List<GeoLocation> locations = locationTree.query(area);
+      Collections.sort(locations, new Comparator<GeoLocation>() {
+         @Override
+         public int compare(GeoLocation l1, GeoLocation l2)
+         {
+            double d1 = Math.pow(Math.pow(l1.getLatitude() - center.getLatitude(), 2) + Math.pow(l1.getLongitude() - center.getLongitude(), 2), 0.5);  
+            double d2 = Math.pow(Math.pow(l2.getLatitude() - center.getLatitude(), 2) + Math.pow(l2.getLongitude() - center.getLongitude(), 2), 0.5);  
+            return (int)Math.signum(d1 - d2);
+         }
+      });
+      
+      return locations;
+   }
+   
+   /**
+    * Updates points for historical view
+    */
+   private void updateHistory()
+   {
+      final NXCSession session = ConsoleSharedData.getSession();
+      ConsoleJob job = new ConsoleJob(Messages.get().GeoMapViewer_DownloadJob_Title, viewPart, Activator.PLUGIN_ID, null) {
+         @Override
+         protected void runInternal(IProgressMonitor monitor) throws Exception
+         {
+            final List<GeoLocation> pl = session.getLocationHistory(historyObject.getObjectId(), timePeriod.getPeriodStart(), timePeriod.getPeriodEnd());
+            runInUIThread(new Runnable() {
+               @Override
+               public void run()
+               {
+                  points = pl;
+                  locationTree.removeAll();
+                  for(int i = 0; i < points.size(); i++)
+                     locationTree.insert(points.get(i).getLatitude(), points.get(i).getLongitude(), points.get(i));
+                  redraw();
+               }
+            });
+         }
+
+         @Override
+         protected String getErrorMessage()
+         {
+            return Messages.get().GeoMapViewer_DownloadError;
+         }
+      };
+      job.setUser(false);
+      job.start();
+   }
+
+   /**
+    * Sets new time period
+    */
+   public void setTimePeriod(TimePeriod timePeriod)
+   {
+      this.timePeriod = timePeriod;
+      updateHistory();      
+   }
+
+   /**
+    * Gets time period
+    */
+   public TimePeriod getTimePeriod()
+   {
+      return timePeriod;
+   }
+
+   /**
+    * @param value
+    * @param unit
+    */
+   public void changeTimePeriod(int value, int unit)
+   {
+      timePeriod.setTimeFrameType(GraphSettings.TIME_FRAME_BACK_FROM_NOW);
+      timePeriod.setTimeRangeValue(value);
+      timePeriod.setTimeUnitValue(unit);
+      updateHistory();
+   }
+
+   /* (non-Javadoc)
+    * @see org.netxms.ui.eclipse.osm.widgets.AbstractGeoMapViewer#drawContent(org.eclipse.swt.graphics.GC, org.netxms.base.GeoLocation, int, int)
+    */
+   @Override
+   protected void drawContent(GC gc, GeoLocation currentLocation, int imgW, int imgH)
+   {
+      final Point centerXY = GeoLocationCache.coordinateToDisplay(currentLocation, accessor.getZoom());
+      int nextX = 0;
+      int nextY = 0;
+      for(int i = 0; i < points.size(); i++)
+      {
+         final Point virtualXY = GeoLocationCache.coordinateToDisplay(points.get(i), accessor.getZoom());
+         final int dx = virtualXY.x - centerXY.x;
+         final int dy = virtualXY.y - centerXY.y;
+         
+         if (i != points.size() - 1)
+         { 
+            final Point virtualXY2 = GeoLocationCache.coordinateToDisplay(points.get(i + 1), accessor.getZoom());
+            nextX = imgW / 2 + (virtualXY2.x - centerXY.x);
+            nextY = imgH / 2 + (virtualXY2.y - centerXY.y);
+         }
+         
+         int color = SWT.COLOR_RED;
+         if (i == selectedPoint)
+         {
+            color = SWT.COLOR_GREEN;
+            DateFormat df = RegionalSettings.getDateTimeFormat();
+            pointToolTip.setText(String.format("%s\r\n%s - %s",  //$NON-NLS-1$
+                  points.get(i), df.format(points.get(i).getTimestamp()), df.format(points.get(i).getEndTimestamp())));
+            pointToolTip.setVisible(true);
+         }
+            
+         if (i == 0)
+         {
+            if (i == points.size() - 1)
+            {
+               nextX = imgW / 2 + dx;
+               nextY = imgH / 2 + dy;
+            }
+            drawPoint(gc, imgW / 2 + dx, imgH / 2 + dy, START, nextX, nextY, color);                  
+            continue;
+         } 
+         
+         if (i == points.size() - 1)
+         {    
+            drawPoint(gc, imgW / 2 + dx, imgH / 2 + dy, END, nextX, nextY, color);
+            continue;
+         }
+         
+         drawPoint(gc, imgW / 2 + dx, imgH / 2 + dy, 0, nextX, nextY, color);
+      }
+   }
+
+   /**
+    * Draw point
+    * 
+    * @param gc
+    * @param x
+    * @param y
+    * @param object
+    */
+   private void drawPoint(GC gc, int x, int y, int flag, int prevX, int prevY, int color) 
+   {    
+      if (flag == START || flag == END)
+      {
+         if (flag == START)
+         {
+            gc.setForeground(TRACK_COLOR);
+            gc.setLineWidth(3);
+            gc.drawLine(x, y, prevX, prevY);
+         }
+         
+         gc.setBackground(getDisplay().getSystemColor(color)); 
+         gc.fillOval(x - 5, y -5, 10, 10);
+         
+         final String text = pointInformation[flag -1];
+         final Point textSize = gc.textExtent(text);
+         
+         Rectangle rect = new Rectangle(x - LABEL_ARROW_OFFSET, y - LABEL_ARROW_HEIGHT - textSize.y, textSize.x
+               + LABEL_X_MARGIN * 2 + LABEL_SPACING, textSize.y + LABEL_Y_MARGIN * 2);
+         
+         gc.setBackground(LABEL_BACKGROUND);
+
+         gc.setForeground(BORDER_COLOR);
+         gc.setLineWidth(4);
+         gc.fillRoundRectangle(rect.x, rect.y, rect.width, rect.height, 8, 8);
+         gc.drawRoundRectangle(rect.x, rect.y, rect.width, rect.height, 8, 8);
+         
+         gc.setLineWidth(2);
+         gc.fillRoundRectangle(rect.x, rect.y, rect.width, rect.height, 8, 8);
+         gc.drawRoundRectangle(rect.x, rect.y, rect.width, rect.height, 8, 8);
+         final int[] arrow = new int[] { rect.x + LABEL_ARROW_OFFSET - 4, rect.y + rect.height, x, y, rect.x + LABEL_ARROW_OFFSET + 4,
+               rect.y + rect.height };
+
+         gc.setLineWidth(4);
+         gc.setForeground(BORDER_COLOR);
+         gc.drawPolyline(arrow);
+
+         gc.fillPolygon(arrow);
+         gc.setForeground(LABEL_BACKGROUND);
+         gc.setLineWidth(2);
+         gc.drawLine(arrow[0], arrow[1], arrow[4], arrow[5]);
+         gc.drawPolyline(arrow);
+
+         gc.setForeground(LABEL_TEXT);
+         gc.drawText(text, rect.x + LABEL_X_MARGIN + LABEL_SPACING, rect.y + LABEL_Y_MARGIN);
+      }
+      else 
+      {
+         gc.setForeground(TRACK_COLOR);
+         gc.setLineWidth(3);
+         gc.drawLine(x, y, prevX, prevY);
+         gc.setBackground(Display.getCurrent().getSystemColor(color)); 
+         gc.fillOval(x - 5, y -5, 10, 10);
+      }      
+   }  
+
+   /* (non-Javadoc)
+    * @see org.netxms.ui.eclipse.osm.widgets.AbstractGeoMapViewer#getObjectAtPoint(org.eclipse.swt.graphics.Point)
+    */
+   @Override
+   public AbstractObject getObjectAtPoint(Point p)
+   {
+      return null;
+   }
+}
diff --git a/webui/webapp/OSM/src/org/netxms/ui/eclipse/osm/widgets/GeoMapViewer.java b/webui/webapp/OSM/src/org/netxms/ui/eclipse/osm/widgets/GeoMapViewer.java
deleted file mode 100644 (file)
index 3a7d794..0000000
+++ /dev/null
@@ -1,1198 +0,0 @@
-/**
- * NetXMS - open source network management system
- * Copyright (C) 2003-2014 Victor Kirhenshtein
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program 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.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package org.netxms.ui.eclipse.osm.widgets;
-
-import java.awt.Polygon;
-import java.text.DateFormat;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import org.eclipse.core.runtime.IProgressMonitor;
-import org.eclipse.core.runtime.IStatus;
-import org.eclipse.core.runtime.Status;
-import org.eclipse.core.runtime.jobs.Job;
-import org.eclipse.jface.resource.JFaceResources;
-import org.eclipse.jface.viewers.ILabelProvider;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.dnd.DND;
-import org.eclipse.swt.dnd.DragSource;
-import org.eclipse.swt.dnd.DragSourceEvent;
-import org.eclipse.swt.dnd.DragSourceListener;
-import org.eclipse.swt.events.DisposeEvent;
-import org.eclipse.swt.events.DisposeListener;
-import org.eclipse.swt.events.MouseEvent;
-import org.eclipse.swt.events.MouseListener;
-import org.eclipse.swt.events.MouseMoveListener;
-import org.eclipse.swt.events.PaintEvent;
-import org.eclipse.swt.events.PaintListener;
-import org.eclipse.swt.graphics.Color;
-import org.eclipse.swt.graphics.Font;
-import org.eclipse.swt.graphics.GC;
-import org.eclipse.swt.graphics.Image;
-import org.eclipse.swt.graphics.Point;
-import org.eclipse.swt.graphics.RGB;
-import org.eclipse.swt.graphics.Rectangle;
-import org.eclipse.swt.widgets.Canvas;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Event;
-import org.eclipse.swt.widgets.Listener;
-import org.eclipse.swt.widgets.ToolTip;
-import org.eclipse.swt.widgets.Widget;
-import org.eclipse.ui.IViewPart;
-import org.eclipse.ui.model.WorkbenchLabelProvider;
-import org.eclipse.ui.presentations.PresentationUtil;
-import org.netxms.base.GeoLocation;
-import org.netxms.client.NXCSession;
-import org.netxms.client.TimePeriod;
-import org.netxms.client.datacollection.GraphSettings;
-import org.netxms.client.objects.AbstractObject;
-import org.netxms.ui.eclipse.console.resources.RegionalSettings;
-import org.netxms.ui.eclipse.console.resources.SharedColors;
-import org.netxms.ui.eclipse.console.resources.SharedIcons;
-import org.netxms.ui.eclipse.console.resources.StatusDisplayInfo;
-import org.netxms.ui.eclipse.jobs.ConsoleJob;
-import org.netxms.ui.eclipse.osm.Activator;
-import org.netxms.ui.eclipse.osm.GeoLocationCache;
-import org.netxms.ui.eclipse.osm.GeoLocationCacheListener;
-import org.netxms.ui.eclipse.osm.Messages;
-import org.netxms.ui.eclipse.osm.tools.Area;
-import org.netxms.ui.eclipse.osm.tools.MapAccessor;
-import org.netxms.ui.eclipse.osm.tools.MapLoader;
-import org.netxms.ui.eclipse.osm.tools.QuadTree;
-import org.netxms.ui.eclipse.osm.tools.Tile;
-import org.netxms.ui.eclipse.osm.tools.TileSet;
-import org.netxms.ui.eclipse.osm.widgets.helpers.GeoMapListener;
-import org.netxms.ui.eclipse.shared.ConsoleSharedData;
-import org.netxms.ui.eclipse.tools.ColorCache;
-import org.netxms.ui.eclipse.tools.ColorConverter;
-
-/**
- * This widget shows map retrieved via OpenStreetMap Static Map API
- */
-public class GeoMapViewer extends Canvas implements PaintListener, GeoLocationCacheListener, MouseListener, MouseMoveListener
-{
-   private static final int START = 1;
-   private static final int END = 2;
-   private final String pointInformation[] = {Messages.get().GeoMapViewer_Start, Messages.get().GeoMapViewer_End};
-   
-       private final Color MAP_BACKGROUND = new Color(Display.getCurrent(), 255, 255, 255);
-   private final Color INFO_BLOCK_BACKGROUND = new Color(Display.getCurrent(), 0, 0, 0);
-   private final Color INFO_BLOCK_TEXT = new Color(Display.getCurrent(), 255, 255, 255);
-       private final Color LABEL_BACKGROUND = new Color(Display.getCurrent(), 240, 254, 192);
-       private final Color LABEL_TEXT = new Color(Display.getCurrent(), 0, 0, 0);
-       private final Color BORDER_COLOR = new Color(Display.getCurrent(), 0, 0, 0);
-   private final Color ACTIVE_BORDER_COLOR = new Color(Display.getCurrent(), 255, 255, 255);
-       private final Color SELECTION_COLOR = new Color(Display.getCurrent(), 0, 0, 255);
-   private final Color TRACK_COLOR = new Color(Display.getCurrent(), 163, 73, 164);
-       
-       private static final Font TITLE_FONT = new Font(Display.getCurrent(), "Verdana", 10, SWT.BOLD);  //$NON-NLS-1$
-
-       private static final int LABEL_ARROW_HEIGHT = 5;
-       private static final int LABEL_ARROW_OFFSET = 10;
-       private static final int LABEL_X_MARGIN = 4;
-       private static final int LABEL_Y_MARGIN = 4;
-       private static final int LABEL_SPACING = 4;
-
-       private static final int DRAG_JITTER = 8;
-
-       private ILabelProvider labelProvider;
-       private Area coverage = new Area(0, 0, 0, 0);
-       private List<AbstractObject> objects = new ArrayList<AbstractObject>();
-       private AbstractObject currentObject = null;
-       private MapAccessor accessor;
-       private MapLoader mapLoader;
-       private IViewPart viewPart = null;
-       private Point currentPoint;
-       private Point dragStartPoint = null;
-       private Point selectionStartPoint = null;
-       private Point selectionEndPoint = null;
-       private Set<GeoMapListener> mapListeners = new HashSet<GeoMapListener>(0);
-       private String title = null;
-       private int offsetX;
-       private int offsetY;
-       private TileSet currentTileSet = null;
-       private RAPDragTracker tracker;
-       private Image imageZoomIn;
-       private Image imageZoomOut;
-       private Rectangle zoomControlRect = null;
-       private boolean historicalData;
-   private List<GeoLocation> history = new ArrayList<GeoLocation>();
-   private QuadTree<GeoLocation> locationTree = new QuadTree<GeoLocation>();
-   private AbstractObject historyObject = null;
-   private TimePeriod timePeriod = new TimePeriod();
-   private int highlightobjectID = -1;
-   private ToolTip toolTip;
-   private ColorCache colorCache;
-   private List<ObjectIcon> objectIcons = new ArrayList<ObjectIcon>();
-       
-       /**
-        * @param parent
-        * @param style
-        */
-       public GeoMapViewer(Composite parent, int style, final boolean historicalData, AbstractObject historyObject)
-       {
-               super(parent, style | SWT.NO_BACKGROUND | SWT.DOUBLE_BUFFERED);
-               this.historicalData = historicalData;
-               if (historicalData)
-               {
-                  this.historyObject = historyObject;             
-               }
-               
-               colorCache = new ColorCache(this);
-               
-               imageZoomIn = Activator.getImageDescriptor("icons/map_zoom_in.png").createImage(); //$NON-NLS-1$
-      imageZoomOut = Activator.getImageDescriptor("icons/map_zoom_out.png").createImage(); //$NON-NLS-1$
-
-               labelProvider = WorkbenchLabelProvider.getDecoratingWorkbenchLabelProvider();
-               mapLoader = new MapLoader(getDisplay());
-
-               setBackground(MAP_BACKGROUND);
-               addPaintListener(this);
-
-               final Runnable timer = new Runnable() {
-                       @Override
-                       public void run()
-                       {
-                               if (isDisposed())
-                                       return;
-
-                               reloadMap();
-                       }
-               };
-
-               addListener(SWT.Resize, new Listener() {
-                       @Override
-                       public void handleEvent(Event event)
-                       {
-                               getDisplay().timerExec(-1, timer);
-                               getDisplay().timerExec(1000, timer);
-                       }
-               });
-
-               addDisposeListener(new DisposeListener() {
-                       @Override
-                       public void widgetDisposed(DisposeEvent e)
-                       {
-                               labelProvider.dispose();
-                               GeoLocationCache.getInstance().removeListener(GeoMapViewer.this);
-                               mapLoader.dispose();
-                               if (currentTileSet != null)
-                               {
-                                       currentTileSet.dispose();
-                                       currentTileSet = null;
-                               }
-                               imageZoomIn.dispose();
-                               imageZoomOut.dispose();
-                       }
-               });
-
-               addMouseListener(this);
-
-               /* the following code is a hack for adding
-                * mouse drag support in RAP. Copied from draw2d
-                * with slight modifications
-                */
-               PresentationUtil.addDragListener(this, new Listener() {
-                       private static final long serialVersionUID = 1L;
-
-                       @Override
-                       public void handleEvent(Event event)
-                       {
-                               if (event.type == SWT.DragDetect) 
-                               {
-                                       MouseEvent me = new MouseEvent(event);
-                                       me.stateMask = SWT.BUTTON1;
-                                       GeoMapViewer.this.mouseDown(me);
-                               }                       
-                       }
-               });
-               DragSource dragSource = new DragSource(this, DND.DROP_MOVE | DND.DROP_COPY | DND.DROP_LINK);
-               final DragSourceListener listener = new DragSourceListener() {
-                       private static final long serialVersionUID = 1L;
-
-                       public void dragStart(DragSourceEvent event)
-                       {
-                               tracker = new RAPDragTracker(GeoMapViewer.this, GeoMapViewer.this);
-                               tracker.open();
-                       }
-
-                       public void dragSetData(DragSourceEvent event)
-                       {
-                       }
-
-                       public void dragFinished(DragSourceEvent event)
-                       {
-                               if (tracker != null)
-                               {
-                                       tracker.close();
-                                       tracker = null;
-                               }
-                       }
-               };
-               dragSource.addDragListener(listener);
-               /* end of mouse drag hack */
-
-               GeoLocationCache.getInstance().addListener(this);
-
-               toolTip = new ToolTip(getShell(), SWT.BALLOON); 
-       }
-       
-       /**
-        * Add zoom level change listener
-        * 
-        * @param listener
-        */
-       public void addMapListener(GeoMapListener listener)
-       {
-               mapListeners.add(listener);
-       }
-       
-       /**
-        * Remove previously registered zoom change listener
-        * 
-        * @param listener
-        */
-       public void removeMapListener(GeoMapListener listener)
-       {
-               mapListeners.remove(listener);
-       }
-       
-       /**
-        * Notify all listeners about zoom level change
-        */
-       private void notifyOnZoomChange()
-       {
-               for(GeoMapListener listener : mapListeners)
-                       listener.onZoom(accessor.getZoom());
-       }
-
-       /**
-        * Notify all listeners about position change
-        */
-       private void notifyOnPositionChange()
-       {
-               for(GeoMapListener listener : mapListeners)
-                       listener.onPan(accessor.getCenterPoint());
-       }
-
-       /**
-        * Show given map
-        * 
-        * @param accessor
-        */
-       public void showMap(MapAccessor accessor)
-       {
-               this.accessor = new MapAccessor(accessor);
-               reloadMap();
-       }
-
-       /**
-        * @param lat
-        * @param lon
-        * @param zoom
-        */
-       public void showMap(double lat, double lon, int zoom)
-       {
-               showMap(new MapAccessor(lat, lon, zoom));
-       }
-
-       /**
-        * Reload current map
-        */
-       private void reloadMap()
-       {
-               Rectangle rect = this.getClientArea();
-               accessor.setMapWidth(rect.width);
-               accessor.setMapHeight(rect.height);
-
-               if (!accessor.isValid())
-                       return;
-
-               final Point mapSize = new Point(accessor.getMapWidth(), accessor.getMapHeight());
-               final GeoLocation centerPoint = accessor.getCenterPoint();
-               ConsoleJob job = new ConsoleJob(Messages.get().GeoMapViewer_DownloadJob_Title, viewPart, Activator.PLUGIN_ID, null) {
-                       @Override
-                       protected void runInternal(IProgressMonitor monitor) throws Exception
-                       {
-                               final TileSet tiles = mapLoader.getAllTiles(mapSize, centerPoint, MapLoader.CENTER, accessor.getZoom(), true);
-                               runInUIThread(new Runnable() {
-                                       @Override
-                                       public void run()
-                                       {
-                                               if (isDisposed())
-                                                       return;
-                                               
-                                               if (currentTileSet != null)
-                                                       currentTileSet.dispose();
-                                               currentTileSet = tiles;
-                                               if ((tiles != null) && (tiles.missingTiles > 0))
-                                                       loadMissingTiles(tiles);
-                                               
-                                               Rectangle clientArea = getClientArea();
-                                               Point mapSize = new Point(clientArea.width, clientArea.height);
-                                               coverage = GeoLocationCache.calculateCoverage(mapSize, accessor.getCenterPoint(), GeoLocationCache.CENTER, accessor.getZoom());
-                                               if (!historicalData)
-                                               {
-                                               objects = GeoLocationCache.getInstance().getObjectsInArea(coverage);
-                                               GeoMapViewer.this.redraw();
-                                               }
-                                               else
-                                               {
-                                                  GeoMapViewer.this.updateHistory();
-                                               }
-                                       }
-                               });
-                       }
-
-                       @Override
-                       protected String getErrorMessage()
-                       {
-                               return Messages.get().GeoMapViewer_DownloadError;
-                       }
-               };
-               job.setUser(false);
-               job.start();
-       }
-       
-       /**
-        * Load missing tiles in tile set
-        * 
-        * @param tiles
-        */
-       private void loadMissingTiles(final TileSet tiles)
-       {
-               ConsoleJob job = new ConsoleJob(Messages.get().GeoMapViewer_LoadMissingJob_Title, viewPart, Activator.PLUGIN_ID, null) {
-                       @Override
-                       protected void runInternal(IProgressMonitor monitor) throws Exception
-                       {
-                               mapLoader.loadMissingTiles(tiles, new Runnable() {
-                                       @Override
-                                       public void run()
-                                       {
-                                               if (!GeoMapViewer.this.isDisposed() && (currentTileSet == tiles))
-                                               {
-                                                       GeoMapViewer.this.redraw();
-                                               }
-                                       }
-                               });
-                       }
-                       
-
-                       @Override
-                       protected IStatus createFailureStatus(Exception e)
-                       {
-                               e.printStackTrace();
-                               return super.createFailureStatus(e);
-                       }
-                       @Override
-                       protected String getErrorMessage()
-                       {
-                               return Messages.get().GeoMapViewer_DownloadError;
-                       }
-               };
-               job.setUser(false);
-               job.start();
-       }
-
-       /**
-        * @param tiles
-        */
-       private void drawTiles(GC gc, TileSet tileSet)
-       {
-               final Tile[][] tiles = tileSet.tiles;
-
-               Point size = getSize();
-
-               int x = tileSet.xOffset;
-               int y = tileSet.yOffset;
-               for(int i = 0; i < tiles.length; i++)
-               {
-                       for(int j = 0; j < tiles[i].length; j++)
-                       {
-                               gc.drawImage(tiles[i][j].getImage(), x, y);
-                               x += 256;
-                               if (x >= size.x)
-                               {
-                                       x = tileSet.xOffset;
-                                       y += 256;
-                               }
-                       }
-               }
-       }
-
-       /*
-        * (non-Javadoc)
-        * @see org.eclipse.swt.events.PaintListener#paintControl(org.eclipse.swt.events.PaintEvent)
-        */
-       @Override
-       public void paintControl(PaintEvent e)
-       {
-               final GC gc = e.gc;
-      gc.setAntialias(SWT.ON);
-      gc.setTextAntialias(SWT.ON);
-      
-      objectIcons.clear();
-
-               if (currentTileSet != null)
-                       drawTiles(gc, currentTileSet);
-               
-               GeoLocation currentLocation;
-
-               // Draw objects and decorations if user is not dragging map
-               // and map is not currently loading
-               if (dragStartPoint == null)
-               {
-         currentLocation = accessor.getCenterPoint();
-         
-                       Rectangle rect = getClientArea();
-                       
-                       final Point centerXY = GeoLocationCache.coordinateToDisplay(currentLocation, accessor.getZoom());
-         if (!historicalData)
-         {
-                       for(AbstractObject object : objects)
-                       {
-                               final Point virtualXY = GeoLocationCache.coordinateToDisplay(object.getGeolocation(), accessor.getZoom());
-                               final int dx = virtualXY.x - centerXY.x;
-                               final int dy = virtualXY.y - centerXY.y;
-                               drawObject(gc, rect.width / 2 + dx, rect.height / 2 + dy, object);
-                       }
-         }
-         else
-         {  
-            int nextX = 0;
-            int nextY = 0;
-            for(int i = 0; i < history.size(); i++)
-            {
-               final Point virtualXY = GeoLocationCache.coordinateToDisplay(history.get(i), accessor.getZoom());
-               final int dx = virtualXY.x - centerXY.x;
-               final int dy = virtualXY.y - centerXY.y;
-               
-               if (i != history.size() - 1)
-               { 
-                  final Point virtualXY2 = GeoLocationCache.coordinateToDisplay(history.get(i+1), accessor.getZoom());
-                  nextX = rect.width / 2 + (virtualXY2.x - centerXY.x);
-                  nextY = rect.height / 2 + (virtualXY2.y - centerXY.y);
-               }
-               
-               int color = SWT.COLOR_RED;
-               if (i == highlightobjectID)
-               {
-                  color = SWT.COLOR_GREEN;
-                  DateFormat df = RegionalSettings.getDateTimeFormat();
-                  toolTip.setText(String.format("%s\r\n%s - %s",
-                        history.get(i), df.format(history.get(i).getTimestamp()), df.format(history.get(i).getEndTimestamp())));
-                  toolTip.setVisible(true);
-               }
-                  
-               if (i == 0)
-               {
-                  if (i == history.size() - 1)
-                  {
-                     nextX = rect.width / 2 + dx;
-                     nextY = rect.height / 2 + dy;
-                  }
-                  drawObject(gc, rect.width / 2 + dx, rect.height / 2 + dy, GeoMapViewer.START, nextX, nextY, color);                  
-                  continue;
-               } 
-               
-               if (i == history.size() - 1)
-               {    
-                  drawObject(gc, rect.width / 2 + dx, rect.height / 2 + dy, GeoMapViewer.END, nextX, nextY, color);
-                  continue;
-               }
-               
-               drawObject(gc, rect.width / 2 + dx, rect.height / 2 + dy, 0, nextX, nextY, color);
-            }
-         }
-               }
-      else
-      {
-         Point cp = GeoLocationCache.coordinateToDisplay(accessor.getCenterPoint(), accessor.getZoom());
-         cp.x += offsetX;
-         cp.y += offsetY;
-         currentLocation = GeoLocationCache.displayToCoordinates(cp, accessor.getZoom());
-      }
-               
-               // Draw selection rectangle
-               if ((selectionStartPoint != null) && (selectionEndPoint != null))
-               {
-                       int x = Math.min(selectionStartPoint.x, selectionEndPoint.x);
-                       int y = Math.min(selectionStartPoint.y, selectionEndPoint.y);
-                       int w = Math.abs(selectionStartPoint.x - selectionEndPoint.x);
-                       int h = Math.abs(selectionStartPoint.y - selectionEndPoint.y);
-                       gc.setBackground(SELECTION_COLOR);
-                       gc.setForeground(SELECTION_COLOR);
-                       gc.setAlpha(64);
-                       gc.fillRectangle(x, y, w, h);
-                       gc.setAlpha(255);
-                       gc.setLineWidth(2);
-                       gc.drawRectangle(x, y, w, h);
-               }
-               
-               // Draw current location info
-      String text = currentLocation.toString();
-      Point textSize = gc.textExtent(text);
-
-      Rectangle rect = getClientArea();
-      rect.x = rect.width - textSize.x - 20;
-      rect.y += 10;
-      rect.width = textSize.x + 10;
-      rect.height = textSize.y + 8;
-
-      gc.setBackground(INFO_BLOCK_BACKGROUND);
-      gc.setAlpha(128);
-      gc.fillRoundRectangle(rect.x, rect.y, rect.width, rect.height, 8, 8);
-      gc.setAlpha(255);
-
-      gc.setForeground(INFO_BLOCK_TEXT);
-      gc.drawText(text, rect.x + 5, rect.y + 4, true);
-               
-               // Draw title
-               if ((title != null) && !title.isEmpty())
-               {
-                       gc.setFont(TITLE_FONT);
-                       rect = getClientArea();
-                       int x = (rect.width - gc.textExtent(title).x) / 2;
-                       gc.setForeground(SharedColors.getColor(SharedColors.GEOMAP_TITLE, getDisplay()));
-                       gc.drawText(title, x, 10, true);
-               }
-               
-               // Draw zoom control
-               gc.setFont(JFaceResources.getHeaderFont());
-               text = Integer.toString(accessor.getZoom());
-               textSize = gc.textExtent(text);
-               
-      rect = getClientArea();
-      rect.x = 10;
-      rect.y = 10;
-      rect.width = 80;
-      rect.height = 47 + textSize.y;
-
-      gc.setBackground(INFO_BLOCK_BACKGROUND);
-      gc.setAlpha(128);
-      gc.fillRoundRectangle(rect.x, rect.y, rect.width, rect.height, 8, 8);
-      gc.setAlpha(255);
-      
-      gc.drawText(text, rect.x + rect.width / 2 - textSize.x / 2, rect.y + 5, true);
-      gc.drawImage(imageZoomIn, rect.x + 5, rect.y + rect.height - 37);
-      gc.drawImage(imageZoomOut, rect.x + 42, rect.y + rect.height - 37);
-      
-      zoomControlRect = rect;
-       }
-
-       /**
-        * Draw object on map
-        * 
-        * @param gc
-        * @param x
-        * @param y
-        * @param object
-        */
-       private void drawObject(GC gc, int x, int y, AbstractObject object) 
-       {
-          boolean selected = (currentObject != null) && (currentObject.getObjectId() == object.getObjectId());
-          
-               Image image = labelProvider.getImage(object);
-               if (image == null)
-                       image = SharedIcons.IMG_UNKNOWN_OBJECT;
-               
-               int w = image.getImageData().width + LABEL_X_MARGIN * 2;
-               int h = image.getImageData().height+ LABEL_Y_MARGIN * 2;
-               Rectangle rect = new Rectangle(x - w / 2 - 1, y - LABEL_ARROW_HEIGHT - h, w, h);
-               
-               Color bgColor = ColorConverter.adjustColor(StatusDisplayInfo.getStatusColor(object.getStatus()), new RGB(255, 255, 255),  0.5f, colorCache);
-               
-      gc.setBackground(bgColor);
-      gc.fillRoundRectangle(rect.x, rect.y, rect.width, rect.height, 4, 4);
-      if (selected)
-      {
-         gc.setLineWidth(3);
-         gc.setForeground(ACTIVE_BORDER_COLOR);
-         gc.drawRoundRectangle(rect.x, rect.y, rect.width, rect.height, 4, 4);
-      }
-      gc.setLineWidth(1);
-      gc.setForeground(BORDER_COLOR);
-               gc.drawRoundRectangle(rect.x, rect.y, rect.width, rect.height, 4, 4);
-               
-               final int[] arrow = new int[] { x - 4, rect.y + rect.height, x, y, x + 4, rect.y + rect.height };
-
-      gc.fillPolygon(arrow);
-      gc.setForeground(bgColor);
-      gc.drawPolygon(arrow);
-      if (selected)
-      {
-         gc.drawLine(arrow[0], arrow[1] - 1, arrow[4], arrow[5] - 1);
-         gc.setLineWidth(3);
-         gc.setForeground(ACTIVE_BORDER_COLOR);
-         gc.drawPolyline(arrow);
-      }
-               gc.setLineWidth(1);
-               gc.setForeground(BORDER_COLOR);
-               gc.drawPolyline(arrow);
-
-               gc.drawImage(image, rect.x + LABEL_X_MARGIN, rect.y + LABEL_Y_MARGIN);
-               
-               objectIcons.add(new ObjectIcon(object, rect, x, y));
-       }
-
-         /**
-    * Draw object on map
-    * 
-    * @param gc
-    * @param x
-    * @param y
-    * @param object
-    */
-   private void drawObject(GC gc, int x, int y, int flag, int prevX, int prevY, int color) 
-   {    
-      if (flag == GeoMapViewer.START || flag == GeoMapViewer.END)
-      {
-         if (flag == GeoMapViewer.START)
-         {
-            gc.setForeground(TRACK_COLOR);
-            gc.setLineWidth(3);
-            gc.drawLine(x, y, prevX, prevY);
-         }
-         
-         gc.setBackground(Display.getCurrent().getSystemColor(color)); 
-         gc.fillOval(x - 5, y -5, 10, 10);
-         
-         final String text = pointInformation[flag -1];
-         final Point textSize = gc.textExtent(text);
-         
-         Rectangle rect = new Rectangle(x - LABEL_ARROW_OFFSET, y - LABEL_ARROW_HEIGHT - textSize.y, textSize.x
-               + LABEL_X_MARGIN * 2 + LABEL_SPACING, textSize.y + LABEL_Y_MARGIN * 2);
-         
-         gc.setBackground(LABEL_BACKGROUND);
-
-         gc.setForeground(BORDER_COLOR);
-         gc.setLineWidth(4);
-         gc.fillRoundRectangle(rect.x, rect.y, rect.width, rect.height, 8, 8);
-         gc.drawRoundRectangle(rect.x, rect.y, rect.width, rect.height, 8, 8);
-         
-         gc.setLineWidth(2);
-         gc.fillRoundRectangle(rect.x, rect.y, rect.width, rect.height, 8, 8);
-         gc.drawRoundRectangle(rect.x, rect.y, rect.width, rect.height, 8, 8);
-         final int[] arrow = new int[] { rect.x + LABEL_ARROW_OFFSET - 4, rect.y + rect.height, x, y, rect.x + LABEL_ARROW_OFFSET + 4,
-               rect.y + rect.height };
-
-         gc.setLineWidth(4);
-         gc.setForeground(BORDER_COLOR);
-         gc.drawPolyline(arrow);
-
-         gc.fillPolygon(arrow);
-         gc.setForeground(LABEL_BACKGROUND);
-         gc.setLineWidth(2);
-         gc.drawLine(arrow[0], arrow[1], arrow[4], arrow[5]);
-         gc.drawPolyline(arrow);
-
-         gc.setForeground(LABEL_TEXT);
-         gc.drawText(text, rect.x + LABEL_X_MARGIN + LABEL_SPACING, rect.y + LABEL_Y_MARGIN);
-      }
-      else 
-      {
-         gc.setForeground(TRACK_COLOR);
-         gc.setLineWidth(3);
-         gc.drawLine(x, y, prevX, prevY);
-         gc.setBackground(Display.getCurrent().getSystemColor(color)); 
-         gc.fillOval(x - 5, y -5, 10, 10);
-      }      
-   }   
-       
-       /*
-        * (non-Javadoc)
-        * 
-        * @see
-        * org.netxms.ui.eclipse.osm.GeoLocationCacheListener#geoLocationCacheChanged
-        * (org.netxms.client.objects.AbstractObject, org.netxms.client.GeoLocation)
-        */
-       @Override
-       public void geoLocationCacheChanged(final AbstractObject object, final GeoLocation prevLocation)
-       {
-               getDisplay().asyncExec(new Runnable() {
-                       @Override
-                       public void run()
-                       {
-                          if (!historicalData)
-                          {
-                             onCacheChange(object, prevLocation);
-                          }
-                          else
-                          {
-                             if (object.getObjectId() == historyObject.getObjectId())
-                                onCacheChange(object, prevLocation);
-                          }
-                       }
-               });
-       }
-
-       /**
-        * Real handler for geolocation cache changes. Must be run in UI thread.
-        * 
-        * @param object
-        * @param prevLocation
-        */
-       private void onCacheChange(final AbstractObject object, final GeoLocation prevLocation)
-       {
-               GeoLocation currLocation = object.getGeolocation();
-               if (((currLocation.getType() != GeoLocation.UNSET) && 
-                     coverage.contains(currLocation.getLatitude(), currLocation.getLongitude()))
-                               || ((prevLocation != null) && (prevLocation.getType() != GeoLocation.UNSET) && 
-                                     coverage.contains(prevLocation.getLatitude(), prevLocation.getLongitude())))
-               {
-                  if (!historicalData)
-                  {
-                       objects = GeoLocationCache.getInstance().getObjectsInArea(coverage);
-                       redraw();
-                  }
-                  else
-                  {
-                     updateHistory();
-                  }
-               }
-       }
-       
-       /* (non-Javadoc)
-        * @see org.eclipse.swt.events.MouseListener#mouseDoubleClick(org.eclipse.swt.events.MouseEvent)
-        */
-       @Override
-       public void mouseDoubleClick(MouseEvent e)
-       {
-       }
-
-       /* (non-Javadoc)
-        * @see org.eclipse.swt.events.MouseListener#mouseDown(org.eclipse.swt.events.MouseEvent)
-        */
-       @Override
-       public void mouseDown(MouseEvent e)
-       {
-               if (e.button == 1) // left button, ignore if map is currently loading
-               {
-                  if (zoomControlRect.contains(e.x, e.y))
-                  {
-                     Rectangle r = new Rectangle(zoomControlRect.x + 5, zoomControlRect.y + zoomControlRect.height - 37, 32, 32);
-                     int zoom = accessor.getZoom();
-                     if (r.contains(e.x, e.y))
-                     {
-                        if (zoom < 18)
-                           zoom++;
-                     }
-                     else
-                     {
-                        r.x += 37;
-               if (r.contains(e.x, e.y))
-               {
-                  if (zoom > 1)
-                     zoom--;
-               }
-                     }
-                     
-                     if (zoom != accessor.getZoom())
-                     {
-                        accessor.setZoom(zoom);
-                        reloadMap();
-                        notifyOnZoomChange();
-                     }
-                  }
-                  else if ((e.stateMask & SWT.SHIFT) != 0)
-                       {
-                               if (accessor.getZoom() < 18)
-                                       selectionStartPoint = new Point(e.x, e.y);
-                       }
-                       else
-                       {
-                               dragStartPoint = new Point(e.x, e.y);
-                               setCursor(getDisplay().getSystemCursor(SWT.CURSOR_SIZEALL));
-                       }
-               }
-
-               currentPoint = new Point(e.x, e.y);
-               if (e.button != 1)
-               {
-               AbstractObject object = getObjectAtPoint(currentPoint);
-               if (object != currentObject)
-                  setCurrentObject(object);
-               }
-       }
-
-       /* (non-Javadoc)
-        * @see org.eclipse.swt.events.MouseListener#mouseUp(org.eclipse.swt.events.MouseEvent)
-        */
-       @Override
-       public void mouseUp(MouseEvent e)
-       {
-               if ((e.button == 1) && (dragStartPoint != null))
-               {
-                       if (Math.abs(offsetX) > DRAG_JITTER || Math.abs(offsetY) > DRAG_JITTER)
-                       {
-                               final Point centerXY = GeoLocationCache.coordinateToDisplay(accessor.getCenterPoint(), accessor.getZoom());
-                               centerXY.x += offsetX;
-                               centerXY.y += offsetY;
-                               final GeoLocation geoLocation = GeoLocationCache.displayToCoordinates(centerXY, accessor.getZoom());
-                               accessor.setLatitude(geoLocation.getLatitude());
-                               accessor.setLongitude(geoLocation.getLongitude());
-                               reloadMap();
-                               notifyOnPositionChange();
-                       }
-                       offsetX = 0;
-                       offsetY = 0;
-                       dragStartPoint = null;
-                       setCursor(null);
-               }
-               if ((e.button == 1) && (selectionStartPoint != null))
-               {
-                       if (selectionEndPoint != null)
-                       {
-                               int x1 = Math.min(selectionStartPoint.x, selectionEndPoint.x);
-                               int x2 = Math.max(selectionStartPoint.x, selectionEndPoint.x);
-                               int y1 = Math.min(selectionStartPoint.y, selectionEndPoint.y);
-                               int y2 = Math.max(selectionStartPoint.y, selectionEndPoint.y);
-
-                               final GeoLocation l1 = getLocationAtPoint(new Point(x1, y1));
-                               final GeoLocation l2 = getLocationAtPoint(new Point(x2, y2));
-                               final GeoLocation lc = getLocationAtPoint(new Point(x2 - (x2 - x1) / 2, y2 - (y2 - y1) / 2));
-
-                               int zoom = accessor.getZoom();
-                               while(zoom < 18)
-                               {
-                                       zoom++;
-                                       final Area area = GeoLocationCache.calculateCoverage(getSize(), lc, GeoLocationCache.CENTER, zoom);
-                                       if (!area.contains(l1.getLatitude(), l1.getLongitude()) ||
-                                           !area.contains(l2.getLatitude(), l2.getLongitude()))
-                                       {
-                                               zoom--;
-                                               break;
-                                       }
-                               }
-
-                               if (zoom != accessor.getZoom())
-                               {
-                                       accessor.setZoom(zoom);
-                                       accessor.setLatitude(lc.getLatitude());
-                                       accessor.setLongitude(lc.getLongitude());
-                                       reloadMap();
-                                       notifyOnPositionChange();
-                                       notifyOnZoomChange();
-                               }
-                       }
-                       selectionStartPoint = null;
-                       selectionEndPoint = null;
-                       redraw();
-               }
-       }
-
-       /* (non-Javadoc)
-        * @see org.eclipse.swt.events.MouseMoveListener#mouseMove(org.eclipse.swt.events.MouseEvent)
-        */
-       @Override
-       public void mouseMove(MouseEvent e)
-       {
-               if (dragStartPoint != null)
-               {
-                       int deltaX = dragStartPoint.x - e.x;
-                       int deltaY = dragStartPoint.y - e.y;
-                       if (Math.abs(deltaX) > DRAG_JITTER || Math.abs(deltaY) > DRAG_JITTER)
-                       {
-                               offsetX = deltaX;
-                               offsetY = deltaY;
-                               redraw();
-                       }
-               }
-               if (selectionStartPoint != null)
-               {
-                       int deltaX = selectionStartPoint.x - e.x;
-                       int deltaY = selectionStartPoint.y - e.y;
-                       if (Math.abs(deltaX) > DRAG_JITTER || Math.abs(deltaY) > DRAG_JITTER)
-                       {
-                               selectionEndPoint = new Point(e.x, e.y);
-                               redraw();
-                       }
-               }
-       }
-
-       /**
-        * @return the currentPoint
-        */
-       public Point getCurrentPoint()
-       {
-               return new Point(currentPoint.x, currentPoint.y);
-       }
-       
-       /**
-        * Get location at given point within widget
-        * 
-        * @param p widget-related coordinates
-        * @return location (latitude/longitude) at given point
-        */
-       public GeoLocation getLocationAtPoint(Point p)
-       {
-               Point cp = GeoLocationCache.coordinateToDisplay(new GeoLocation(coverage.getxHigh(), coverage.getyLow()), accessor.getZoom());
-               return GeoLocationCache.displayToCoordinates(new Point(cp.x + p.x, cp.y + p.y), accessor.getZoom());
-       }
-       
-       /**
-    * Get object at given point within widget
-    * 
-    * @param p widget-related coordinates
-    * @return object at given point or null
-        */
-       public AbstractObject getObjectAtPoint(Point p)
-       {
-          for(int i = objectIcons.size() - 1; i >= 0; i--)
-          {
-             ObjectIcon icon = objectIcons.get(i);
-             if (icon.contains(p))
-                return icon.object;
-          }
-          return null;
-       }
-
-       /**
-        * @return the viewPart
-        */
-       public IViewPart getViewPart()
-       {
-               return viewPart;
-       }
-
-       /**
-        * @param viewPart the viewPart to set
-        */
-       public void setViewPart(IViewPart viewPart)
-       {
-               this.viewPart = viewPart;
-       }
-
-       /**
-        * @return the title
-        */
-       public String getTitle()
-       {
-               return title;
-       }
-
-       /**
-        * @param title the title to set
-        */
-       public void setTitle(String title)
-       {
-               this.title = title;
-       }
-       
-       /**
-        * Drag tracker - copied from draw2d
-        *
-        */
-       protected class RAPDragTracker
-       {
-               public boolean cancelled;
-               public boolean tracking;
-               private final MouseMoveListener listener;
-               private final Widget widget;
-
-               public RAPDragTracker(final MouseMoveListener listener, final Widget widget)
-               {
-                       this.listener = listener;
-                       this.widget = widget;
-               }
-
-               public void open()
-               {
-                       Job dragJob = new Job("Drag-Job") {
-                               protected IStatus run(IProgressMonitor monitor)
-                               {
-                                       // Run tracker until mouse up occurs or escape key pressed.
-                                       final Display display = widget.getDisplay();
-                                       cancelled = false;
-                                       tracking = true;
-
-                                       try
-                                       {
-                                               long timeout = 0;
-                                               long refreshRate = 200;
-                                               while(tracking && !cancelled)
-                                               {
-                                                       if (!display.isDisposed())
-                                                       {
-                                                               display.syncExec(new Runnable()
-                                                               {
-                                                                       public void run()
-                                                                       {
-                                                                               if (GeoMapViewer.this.isDisposed())
-                                                                               {
-                                                                                       tracking = false;
-                                                                                       cancelled = true;
-                                                                                       return;
-                                                                               }
-                                                                               Event ev = new Event();
-                                                                               ev.display = display;
-                                                                               Point loc = GeoMapViewer.this.toControl(display.getCursorLocation());
-                                                                               ev.type = SWT.DragDetect;
-                                                                               ev.widget = widget;
-                                                                               ev.button = 1;
-                                                                               ev.x = loc.x;
-                                                                               ev.y = loc.y;
-                                                                               MouseEvent me = new MouseEvent(ev);
-                                                                               me.stateMask = SWT.BUTTON1;
-                                                                               listener.mouseMove(me);
-                                                                       }
-                                                               });
-                                                               timeout += refreshRate;
-                                                               if (timeout >= 60000)
-                                                               {
-                                                                       cancelled = true;
-                                                               }
-                                                               Thread.sleep(refreshRate);
-                                                       }
-
-                                               }
-                                       }
-                                       catch(InterruptedException e)
-                                       {
-                                               e.printStackTrace();
-                                       }
-                                       finally
-                                       {
-                                               display.syncExec(new Runnable()
-                                               {
-                                                       public void run()
-                                                       {
-                                                               close();
-                                                       }
-                                               });
-                                       }
-                                       return Status.OK_STATUS;
-                               }
-                       };
-                       dragJob.setSystem(true);
-                       dragJob.schedule();
-               }
-
-               public void close()
-               {
-                       tracking = false;
-               }
-       }
-
-       /**
-        * Updates points for historical view
-        */
-       private void updateHistory()
-       {
-       final NXCSession session = (NXCSession)ConsoleSharedData.getSession();
-          ConsoleJob job = new ConsoleJob(Messages.get().GeoMapViewer_DownloadJob_Title, viewPart, Activator.PLUGIN_ID, null) {
-         @Override
-         protected void runInternal(IProgressMonitor monitor) throws Exception
-         {
-            history = session.getLocationHistory(historyObject.getObjectId(), timePeriod.getPeriodStart(), timePeriod.getPeriodEnd());
-            for(int i = 0; i < history.size(); i++)
-               locationTree.insert(history.get(i).getLatitude(), history.get(i).getLongitude(), history.get(i));
-            
-            runInUIThread(new Runnable() {
-               @Override
-               public void run()
-               {
-                  GeoMapViewer.this.redraw();
-               }
-            });
-         }
-
-         @Override
-         protected String getErrorMessage()
-         {
-            return Messages.get().GeoMapViewer_DownloadError;
-         }
-      };
-      job.setUser(false);
-      job.start();
-       }
-       
-       /**
-        * Sets new time period
-        */
-   public void setTimePeriod(TimePeriod timePeriod)
-   {
-      this.timePeriod = timePeriod;
-      updateHistory();      
-   }
-
-   /**
-    * Gets time period
-    */
-   public TimePeriod getTimePeriod()
-   {
-      return timePeriod;
-   }
-
-   /**
-    * @param value
-    * @param unit
-    */
-   public void changeTimePeriod(int value, int unit)
-   {
-      timePeriod.setTimeFrameType(GraphSettings.TIME_FRAME_BACK_FROM_NOW);
-      timePeriod.setTimeRangeValue(value);
-      timePeriod.setTimeUnitValue(unit);
-      updateHistory();
-   }
-   
-   /**
-    * Set current object
-    * 
-    * @param object
-    */
-   private void setCurrentObject(AbstractObject object)
-   {
-      currentObject = object;
-      if (currentObject != null)
-      {
-         int idx = objects.indexOf(currentObject);
-         objects.remove(idx);
-         objects.add(currentObject);
-      }
-      redraw();
-   }
-
-   /**
-    * Object icon on map
-    */
-   private class ObjectIcon
-   {
-      public Rectangle rect;
-      public Polygon arrow;
-      public AbstractObject object;
-
-      public ObjectIcon(AbstractObject object, Rectangle rect, int x, int y)
-      {
-         this.rect = rect;
-         this.object = object;
-         arrow = new Polygon();
-         arrow.addPoint(x, y);
-         arrow.addPoint(x - 4, rect.y + rect.height);
-         arrow.addPoint(x + 4, rect.y + rect.height);
-      }
-      
-      public boolean contains(Point p)
-      {
-         return rect.contains(p) || arrow.contains(p.x, p.y); 
-      }
-   }
-}
diff --git a/webui/webapp/OSM/src/org/netxms/ui/eclipse/osm/widgets/ObjectGeoLocationViewer.java b/webui/webapp/OSM/src/org/netxms/ui/eclipse/osm/widgets/ObjectGeoLocationViewer.java
new file mode 100644 (file)
index 0000000..b40ffc5
--- /dev/null
@@ -0,0 +1,365 @@
+/**
+ * NetXMS - open source network management system
+ * Copyright (C) 2003-2015 Victor Kirhenshtein
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package org.netxms.ui.eclipse.osm.widgets;
+
+import java.awt.Polygon;
+import java.util.ArrayList;
+import java.util.List;
+import org.eclipse.jface.resource.JFaceResources;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.DisposeEvent;
+import org.eclipse.swt.events.DisposeListener;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.RGB;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.netxms.base.GeoLocation;
+import org.netxms.client.objects.AbstractObject;
+import org.netxms.ui.eclipse.console.resources.SharedIcons;
+import org.netxms.ui.eclipse.console.resources.StatusDisplayInfo;
+import org.netxms.ui.eclipse.osm.GeoLocationCache;
+import org.netxms.ui.eclipse.tools.ColorConverter;
+import org.netxms.ui.eclipse.tools.FontTools;
+
+/**
+ * Geo location viewer for objects
+ */
+public class ObjectGeoLocationViewer extends AbstractGeoMapViewer
+{
+   private static final int OBJECT_TOOLTIP_X_MARGIN = 6;
+   private static final int OBJECT_TOOLTIP_Y_MARGIN = 6;
+   private static final int OBJECT_TOOLTIP_SPACING = 6;
+
+   private static final Color ACTIVE_BORDER_COLOR = new Color(Display.getCurrent(), 255, 255, 255);
+   
+   private List<AbstractObject> objects = new ArrayList<AbstractObject>();
+   private AbstractObject currentObject = null;
+   private List<ObjectIcon> objectIcons = new ArrayList<ObjectIcon>();
+   private Point objectToolTipLocation = null;
+   private Rectangle objectTooltipRectangle = null;
+   private Font objectToolTipHeaderFont;
+   
+   /**
+    * @param parent
+    * @param style
+    */
+   public ObjectGeoLocationViewer(Composite parent, int style)
+   {
+      super(parent, style);
+
+      objectToolTipHeaderFont = FontTools.createFont(TITLE_FONTS, 1, SWT.BOLD);
+      
+      addDisposeListener(new DisposeListener() {
+         @Override
+         public void widgetDisposed(DisposeEvent e)
+         {
+            objectToolTipHeaderFont.dispose();
+         }
+      });
+   }
+
+   /* (non-Javadoc)
+    * @see org.netxms.ui.eclipse.osm.widgets.AbstractGeoMapViewer#onMapLoad()
+    */
+   @Override
+   protected void onMapLoad()
+   {
+      objects = GeoLocationCache.getInstance().getObjectsInArea(coverage);
+      redraw();
+   }
+
+   /* (non-Javadoc)
+    * @see org.netxms.ui.eclipse.osm.widgets.AbstractGeoMapViewer#onCacheChange(org.netxms.client.objects.AbstractObject, org.netxms.base.GeoLocation)
+    */
+   @Override
+   protected void onCacheChange(AbstractObject object, GeoLocation prevLocation)
+   {
+      GeoLocation currLocation = object.getGeolocation();
+      if (((currLocation.getType() != GeoLocation.UNSET) && 
+            coverage.contains(currLocation.getLatitude(), currLocation.getLongitude()))
+            || ((prevLocation != null) && (prevLocation.getType() != GeoLocation.UNSET) && 
+                  coverage.contains(prevLocation.getLatitude(), prevLocation.getLongitude())))
+      {
+         objects = GeoLocationCache.getInstance().getObjectsInArea(coverage);
+         redraw();
+      }
+   }
+
+   /* (non-Javadoc)
+    * @see org.netxms.ui.eclipse.osm.widgets.AbstractGeoMapViewer#mouseMove(org.eclipse.swt.events.MouseEvent)
+    */
+   @Override
+   public void mouseMove(MouseEvent e)
+   {
+      super.mouseMove(e);
+      if ((objectTooltipRectangle != null) && !objectTooltipRectangle.contains(e.x, e.y))
+      {
+         objectTooltipRectangle = null;
+         objectToolTipLocation = null;
+         currentObject = null;
+         redraw();
+      }
+   }
+
+   /* (non-Javadoc)
+    * @see org.netxms.ui.eclipse.osm.widgets.AbstractGeoMapViewer#mouseDown(org.eclipse.swt.events.MouseEvent)
+    */
+   @Override
+   public void mouseDown(MouseEvent e)
+   {
+      super.mouseDown(e);
+      if (e.button != 1)
+      {
+         AbstractObject object = getObjectAtPoint(currentPoint);
+         if (object != currentObject)
+            setCurrentObject(object);
+      }
+   }
+
+   /**
+    * Set current object
+    * 
+    * @param object
+    */
+   private void setCurrentObject(AbstractObject object)
+   {
+      currentObject = object;
+      if (currentObject != null)
+      {
+         int idx = objects.indexOf(currentObject);
+         objects.remove(idx);
+         objects.add(currentObject);
+      }
+      redraw();
+   }
+
+   /* (non-Javadoc)
+    * @see org.netxms.ui.eclipse.osm.widgets.AbstractGeoMapViewer#drawContent(org.eclipse.swt.graphics.GC, org.netxms.base.GeoLocation, int, int)
+    */
+   @Override
+   protected void drawContent(GC gc, GeoLocation currentLocation, int imgW, int imgH)
+   {
+      objectIcons.clear();
+      
+      final Point centerXY = GeoLocationCache.coordinateToDisplay(currentLocation, accessor.getZoom());
+      for(AbstractObject object : objects)
+      {
+         final Point virtualXY = GeoLocationCache.coordinateToDisplay(object.getGeolocation(), accessor.getZoom());
+         final int dx = virtualXY.x - centerXY.x;
+         final int dy = virtualXY.y - centerXY.y;
+         drawObject(gc, imgW / 2 + dx, imgH / 2 + dy, object);
+      }
+      if (objectToolTipLocation != null)
+         drawObjectToolTip(gc);
+   }
+
+   /**
+    * Draw object on map
+    * 
+    * @param gc
+    * @param x
+    * @param y
+    * @param object
+    */
+   private void drawObject(GC gc, int x, int y, AbstractObject object) 
+   {
+      boolean selected = (currentObject != null) && (currentObject.getObjectId() == object.getObjectId());
+      
+      Image image = labelProvider.getImage(object);
+      if (image == null)
+         image = SharedIcons.IMG_UNKNOWN_OBJECT;
+      
+      int w = image.getImageData().width + LABEL_X_MARGIN * 2;
+      int h = image.getImageData().height+ LABEL_Y_MARGIN * 2;
+      Rectangle rect = new Rectangle(x - w / 2 - 1, y - LABEL_ARROW_HEIGHT - h, w, h);
+      
+      Color bgColor = ColorConverter.adjustColor(StatusDisplayInfo.getStatusColor(object.getStatus()), new RGB(255, 255, 255),  0.5f, colorCache);
+      
+      gc.setBackground(bgColor);
+      gc.fillRoundRectangle(rect.x, rect.y, rect.width, rect.height, 4, 4);
+      if (selected)
+      {
+         gc.setLineWidth(3);
+         gc.setForeground(ACTIVE_BORDER_COLOR);
+         gc.drawRoundRectangle(rect.x, rect.y, rect.width, rect.height, 4, 4);
+      }
+      gc.setLineWidth(1);
+      gc.setForeground(BORDER_COLOR);
+      gc.drawRoundRectangle(rect.x, rect.y, rect.width, rect.height, 4, 4);
+      
+      final int[] arrow = new int[] { x - 4, rect.y + rect.height, x, y, x + 4, rect.y + rect.height };
+
+      gc.fillPolygon(arrow);
+      gc.setForeground(bgColor);
+      gc.drawPolygon(arrow);
+      if (selected)
+      {
+         gc.drawLine(arrow[0], arrow[1] - 1, arrow[4], arrow[5] - 1);
+         gc.setLineWidth(3);
+         gc.setForeground(ACTIVE_BORDER_COLOR);
+         gc.drawPolyline(arrow);
+      }
+      gc.setLineWidth(1);
+      gc.setForeground(BORDER_COLOR);
+      gc.drawPolyline(arrow);
+
+      gc.drawImage(image, rect.x + LABEL_X_MARGIN, rect.y + LABEL_Y_MARGIN);
+      
+      objectIcons.add(new ObjectIcon(object, rect, x, y));
+   }
+   
+   /**
+    * Draw tooltip for current object
+    * 
+    * @param gc
+    */
+   private void drawObjectToolTip(GC gc)
+   {
+      gc.setFont(objectToolTipHeaderFont);
+      Point titleSize = gc.textExtent(currentObject.getObjectName());
+      gc.setFont(JFaceResources.getDefaultFont());
+      
+      // Calculate width and height
+      int width = Math.max(titleSize.x + 12, 128);
+      int height = OBJECT_TOOLTIP_Y_MARGIN * 2 + titleSize.y + 2 + OBJECT_TOOLTIP_SPACING;
+      
+      final String location = currentObject.getGeolocation().toString();
+      Point pt = gc.textExtent(location);
+      if (width < pt.x)
+         width = pt.x;
+      height += pt.y;
+      
+      final String postalAddress = currentObject.getPostalAddress().getAddressLine();
+      if (!postalAddress.isEmpty())
+      {
+         pt = gc.textExtent(postalAddress);
+         if (width < pt.x)
+            width = pt.x;
+         height += pt.y + OBJECT_TOOLTIP_SPACING;
+      }
+      
+      if ((currentObject.getComments() != null) && !currentObject.getComments().isEmpty())
+      {
+         pt = gc.textExtent(currentObject.getComments());
+         if (width < pt.x)
+            width = pt.x;
+         height += pt.y + OBJECT_TOOLTIP_SPACING * 2 + 1;
+      }
+      
+      width += OBJECT_TOOLTIP_X_MARGIN * 2;
+      
+      Rectangle ca = getClientArea();
+      Rectangle rect = new Rectangle(objectToolTipLocation.x - width / 2, objectToolTipLocation.y - height / 2, width, height);
+      if (rect.x < 0)
+         rect.x = 0;
+      else if (rect.x + rect.width >= ca.width)
+         rect.x = ca.width - rect.width - 1;
+      if (rect.y < 0)
+         rect.y = 0;
+      else if (rect.y + rect.height  >= ca.height)
+         rect.y = ca.height - rect.height - 1;
+      
+      gc.setBackground(colorCache.create(224, 224, 224));
+      gc.setAlpha(192);
+      gc.fillRoundRectangle(rect.x, rect.y, rect.width, rect.height, 3, 3);
+      
+      gc.setForeground(colorCache.create(92, 92, 92));
+      gc.setAlpha(255);
+      gc.setLineWidth(3);
+      gc.drawRoundRectangle(rect.x, rect.y, rect.width, rect.height, 3, 3);
+      gc.setLineWidth(1);
+      int y = rect.y + OBJECT_TOOLTIP_Y_MARGIN + titleSize.y + 2;
+      gc.drawLine(rect.x + 1, y, rect.x + rect.width - 1, y);
+      
+      gc.setBackground(StatusDisplayInfo.getStatusColor(currentObject.getStatus()));
+      gc.fillOval(rect.x + OBJECT_TOOLTIP_X_MARGIN, rect.y + OBJECT_TOOLTIP_Y_MARGIN + titleSize.y / 2 - 4, 8, 8);
+      
+      gc.setForeground(colorCache.create(0, 0, 0));
+      gc.setFont(objectToolTipHeaderFont);
+      gc.drawText(currentObject.getObjectName(), rect.x + OBJECT_TOOLTIP_X_MARGIN + 12, rect.y + OBJECT_TOOLTIP_Y_MARGIN, true);
+      
+      gc.setFont(JFaceResources.getDefaultFont());
+      int textLineHeight = gc.textExtent("M").y;
+      y = rect.y + OBJECT_TOOLTIP_Y_MARGIN + titleSize.y + OBJECT_TOOLTIP_SPACING + 2;
+      gc.drawText(location, rect.x + OBJECT_TOOLTIP_X_MARGIN, y, true);
+      if (!postalAddress.isEmpty())
+      {
+         y += textLineHeight;
+         gc.drawText(postalAddress, rect.x + OBJECT_TOOLTIP_X_MARGIN, y, true);
+      }
+      
+      if ((currentObject.getComments() != null) && !currentObject.getComments().isEmpty())
+      {
+         y += textLineHeight + OBJECT_TOOLTIP_SPACING;
+         gc.setForeground(colorCache.create(92, 92, 92));
+         gc.drawLine(rect.x + 1, y, rect.x + rect.width - 1, y);
+         y += OBJECT_TOOLTIP_SPACING;
+         gc.setForeground(colorCache.create(0, 0, 0));
+         gc.drawText(currentObject.getComments(), rect.x + OBJECT_TOOLTIP_X_MARGIN, y, true);
+      }
+      
+      objectTooltipRectangle = rect;
+   }
+
+   /* (non-Javadoc)
+    * @see org.netxms.ui.eclipse.osm.widgets.AbstractGeoMapViewer#getObjectAtPoint(org.eclipse.swt.graphics.Point)
+    */
+   @Override
+   public AbstractObject getObjectAtPoint(Point p)
+   {
+      for(int i = objectIcons.size() - 1; i >= 0; i--)
+      {
+         ObjectIcon icon = objectIcons.get(i);
+         if (icon.contains(p))
+            return icon.object;
+      }
+      return null;
+   }
+
+   /**
+    * Object icon on map
+    */
+   private class ObjectIcon
+   {
+      public Rectangle rect;
+      public Polygon arrow;
+      public AbstractObject object;
+
+      public ObjectIcon(AbstractObject object, Rectangle rect, int x, int y)
+      {
+         this.rect = rect;
+         this.object = object;
+         arrow = new Polygon();
+         arrow.addPoint(x, y);
+         arrow.addPoint(x - 4, rect.y + rect.height);
+         arrow.addPoint(x + 4, rect.y + rect.height);
+      }
+      
+      public boolean contains(Point p)
+      {
+         return rect.contains(p) || arrow.contains(p.x, p.y); 
+      }
+   }
+}