org.netxms.ui.eclipse.objectview.widgets.RackWidget.java Source code

Java tutorial

Introduction

Here is the source code for org.netxms.ui.eclipse.objectview.widgets.RackWidget.java

Source

/**
 * NetXMS - open source network management system
 * Copyright (C) 2003-2016 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.objectview.widgets;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
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.MouseListener;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.MouseTrackListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
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.netxms.base.NXCommon;
import org.netxms.client.datacollection.DciValue;
import org.netxms.client.objects.AbstractNode;
import org.netxms.client.objects.AbstractObject;
import org.netxms.client.objects.DataCollectionTarget;
import org.netxms.client.objects.Rack;
import org.netxms.client.objects.RackElement;
import org.netxms.ui.eclipse.console.resources.SharedColors;
import org.netxms.ui.eclipse.console.resources.StatusDisplayInfo;
import org.netxms.ui.eclipse.imagelibrary.shared.ImageProvider;
import org.netxms.ui.eclipse.imagelibrary.shared.ImageUpdateListener;
import org.netxms.ui.eclipse.objectview.Activator;
import org.netxms.ui.eclipse.objectview.widgets.helpers.RackSelectionListener;
import org.netxms.ui.eclipse.tools.ColorCache;
import org.netxms.ui.eclipse.tools.FontTools;
import org.netxms.ui.eclipse.tools.WidgetHelper;

/**
 * Rack display widget
 */
public class RackWidget extends Canvas implements PaintListener, DisposeListener, ImageUpdateListener,
        MouseListener, MouseTrackListener, MouseMoveListener {
    private static final double UNIT_WH_RATIO = 10.85;
    private static final int BORDER_WIDTH_RATIO = 15;
    private static final int FULL_UNIT_WIDTH = 482;
    private static final int FULL_UNIT_HEIGHT = 45;
    private static final int MARGIN_HEIGHT = 10;
    private static final int MARGIN_WIDTH = 10;
    private static final int UNIT_NUMBER_WIDTH = 30;
    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 String[] FONT_NAMES = { "Segoe UI", "Liberation Sans", "DejaVu Sans", "Verdana", "Arial" };

    private Rack rack;
    private Font[] labelFonts;
    private Image imageDefaultTop;
    private Image imageDefaultMiddle;
    private Image imageDefaultBottom;
    private List<ObjectImage> objects = new ArrayList<ObjectImage>();
    private AbstractObject selectedObject = null;
    private Set<RackSelectionListener> selectionListeners = new HashSet<RackSelectionListener>(0);
    private Point objectToolTipLocation = null;
    private Rectangle objectTooltipRectangle = null;
    private Font objectToolTipHeaderFont;
    private AbstractObject tooltipObject = null;
    private ColorCache colorCache;

    /**
     * @param parent
     * @param style
     */
    public RackWidget(Composite parent, int style, Rack rack) {
        super(parent, style | SWT.DOUBLE_BUFFERED);
        this.rack = rack;

        colorCache = new ColorCache(this);

        objectToolTipHeaderFont = FontTools.createFont(FONT_NAMES, 1, SWT.BOLD);

        setBackground(SharedColors.getColor(SharedColors.RACK_BACKGROUND, getDisplay()));

        final String fontName = FontTools.findFirstAvailableFont(FONT_NAMES);
        labelFonts = new Font[16];
        for (int i = 0; i < labelFonts.length; i++)
            labelFonts[i] = new Font(getDisplay(), fontName, i + 6, SWT.NORMAL);

        imageDefaultTop = Activator.getImageDescriptor("icons/rack-default-top.png").createImage();
        imageDefaultMiddle = Activator.getImageDescriptor("icons/rack-default-middle.png").createImage();
        imageDefaultBottom = Activator.getImageDescriptor("icons/rack-default-bottom.png").createImage();

        addPaintListener(this);
        addMouseListener(this);
        //addMouseTrackListener(this);
        //addMouseMoveListener(this);
        addDisposeListener(this);
        ImageProvider.getInstance().addUpdateListener(this);
    }

    /**
     * @return the currentObject
     */
    public AbstractObject getCurrentObject() {
        return selectedObject;
    }

    /* (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.setInterpolation(SWT.HIGH);

        // Calculate bounding box for rack picture
        Rectangle rect = getClientArea();
        rect.x += MARGIN_WIDTH + UNIT_NUMBER_WIDTH;
        rect.y += MARGIN_HEIGHT;
        rect.height -= MARGIN_HEIGHT * 2;

        // Estimated unit width/height and calculate border width
        double unitHeight = (double) rect.height / (double) rack.getHeight();
        int unitWidth = (int) (unitHeight * UNIT_WH_RATIO);
        int borderWidth = unitWidth / BORDER_WIDTH_RATIO;
        if (borderWidth < 3)
            borderWidth = 3;
        rect.height -= borderWidth;

        // precise unit width and height taking borders into consideration
        unitHeight = (double) (rect.height - ((borderWidth + 1) / 2) * 2) / (double) rack.getHeight();
        unitWidth = (int) (unitHeight * UNIT_WH_RATIO);
        rect.width = unitWidth + borderWidth * 2;

        // Rack itself
        gc.setBackground(SharedColors.getColor(SharedColors.RACK_EMPTY_SPACE, getDisplay()));
        gc.fillRoundRectangle(rect.x, rect.y, rect.width, rect.height, 3, 3);
        gc.setLineWidth(borderWidth);
        gc.setForeground(SharedColors.getColor(SharedColors.RACK_BORDER, getDisplay()));
        gc.drawRoundRectangle(rect.x, rect.y, rect.width, rect.height, 3, 3);

        // Rack bottom
        gc.setBackground(SharedColors.getColor(SharedColors.RACK_BORDER, getDisplay()));
        gc.fillRectangle(rect.x + borderWidth * 2 - (borderWidth + 1) / 2, rect.y + rect.height, borderWidth * 2,
                (int) (borderWidth * 1.5));
        gc.fillRectangle(rect.x + rect.width - borderWidth * 3 - (borderWidth + 1) / 2, rect.y + rect.height,
                borderWidth * 2, (int) (borderWidth * 1.5));

        // Draw unit numbers
        int[] unitBaselines = new int[rack.getHeight() + 1];
        gc.setFont(WidgetHelper.getBestFittingFont(gc, labelFonts, "00", UNIT_NUMBER_WIDTH, (int) unitHeight - 2));
        gc.setForeground(SharedColors.getColor(SharedColors.RACK_TEXT, getDisplay()));
        gc.setBackground(SharedColors.getColor(SharedColors.RACK_BACKGROUND, getDisplay()));
        gc.setLineWidth(1);
        double dy = rack.isTopBottomNumbering() ? rect.y + unitHeight + (borderWidth + 1) / 2
                : rect.y + rect.height - (borderWidth + 1) / 2;
        if (rack.isTopBottomNumbering())
            unitBaselines[0] = (int) (dy - unitHeight);
        for (int u = 1; u <= rack.getHeight(); u++) {
            int y = (int) dy;
            gc.drawLine(MARGIN_WIDTH, y, UNIT_NUMBER_WIDTH, y);
            String label = Integer.toString(u);
            Point textExtent = gc.textExtent(label);
            gc.drawText(label, UNIT_NUMBER_WIDTH - textExtent.x, y - (int) unitHeight / 2 - textExtent.y / 2);
            if (rack.isTopBottomNumbering()) {
                unitBaselines[u] = y;
                dy += unitHeight;
            } else {
                unitBaselines[u - 1] = y;
                dy -= unitHeight;
            }
        }
        if (!rack.isTopBottomNumbering())
            unitBaselines[rack.getHeight()] = (int) dy;

        // Draw units
        objects.clear();
        List<RackElement> units = rack.getUnits();
        for (RackElement n : units) {
            if ((n.getRackPosition() < 1) || (n.getRackPosition() > rack.getHeight())
                    || (rack.isTopBottomNumbering()
                            && (n.getRackPosition() + n.getRackHeight() > rack.getHeight() + 1))
                    || (!rack.isTopBottomNumbering() && (n.getRackPosition() - n.getRackHeight() < 0)))
                continue;

            int topLine, bottomLine;
            if (rack.isTopBottomNumbering()) {
                bottomLine = unitBaselines[n.getRackPosition() + n.getRackHeight() - 1]; // lower border
                topLine = unitBaselines[n.getRackPosition() - 1]; // upper border
            } else {
                bottomLine = unitBaselines[n.getRackPosition() - n.getRackHeight()]; // lower border
                topLine = unitBaselines[n.getRackPosition()]; // upper border
            }
            final Rectangle unitRect = new Rectangle(rect.x + (borderWidth + 1) / 2, topLine + 1,
                    rect.width - borderWidth, bottomLine - topLine);

            if ((unitRect.width <= 0) || (unitRect.height <= 0))
                break;

            objects.add(new ObjectImage(n, unitRect));

            // draw status indicator
            gc.setBackground(StatusDisplayInfo.getStatusColor(n.getStatus()));
            gc.fillRectangle(unitRect.x - borderWidth + borderWidth / 4 + 1, unitRect.y + 1, borderWidth / 2 - 1,
                    Math.min(borderWidth, (int) unitHeight - 2));

            if ((n.getRackImage() != null) && !n.getRackImage().equals(NXCommon.EMPTY_GUID)) {
                Image image = ImageProvider.getInstance().getImage(n.getRackImage());
                Rectangle r = image.getBounds();
                gc.drawImage(image, 0, 0, r.width, r.height, unitRect.x, unitRect.y, unitRect.width,
                        unitRect.height);
            } else // Draw default representation
            {
                Rectangle r = imageDefaultTop.getBounds();
                if (n.getRackHeight() == 1) {
                    gc.drawImage(imageDefaultTop, 0, 0, r.width, r.height, unitRect.x, unitRect.y, unitRect.width,
                            unitRect.height);
                } else {
                    if (rack.isTopBottomNumbering()) {
                        unitRect.height = unitBaselines[n.getRackPosition()] - topLine;
                        gc.drawImage(imageDefaultTop, 0, 0, r.width, r.height, unitRect.x, unitRect.y,
                                unitRect.width, unitRect.height);

                        r = imageDefaultMiddle.getBounds();
                        int u = n.getRackPosition() + 1;
                        for (int i = 1; i < n.getRackHeight() - 1; i++, u++) {
                            unitRect.y = unitBaselines[u - 1];
                            unitRect.height = unitBaselines[u] - unitRect.y;
                            gc.drawImage(imageDefaultMiddle, 0, 0, r.width, r.height, unitRect.x, unitRect.y,
                                    unitRect.width, unitRect.height);
                        }

                        r = imageDefaultBottom.getBounds();
                        unitRect.y = unitBaselines[u - 1];
                        unitRect.height = unitBaselines[u] - unitRect.y;
                        gc.drawImage(imageDefaultBottom, 0, 0, r.width, r.height, unitRect.x, unitRect.y,
                                unitRect.width, unitRect.height);
                    } else {
                        unitRect.height = unitBaselines[n.getRackPosition() - 1] - topLine;
                        gc.drawImage(imageDefaultTop, 0, 0, r.width, r.height, unitRect.x, unitRect.y,
                                unitRect.width, unitRect.height);

                        r = imageDefaultMiddle.getBounds();
                        int u = n.getRackPosition() - 1;
                        for (int i = 1; i < n.getRackHeight() - 1; i++, u--) {
                            unitRect.y = unitBaselines[u];
                            unitRect.height = unitBaselines[u - 1] - unitRect.y;
                            gc.drawImage(imageDefaultMiddle, 0, 0, r.width, r.height, unitRect.x, unitRect.y,
                                    unitRect.width, unitRect.height);
                        }

                        r = imageDefaultBottom.getBounds();
                        unitRect.y = unitBaselines[u];
                        unitRect.height = unitBaselines[u - 1] - unitRect.y;
                        gc.drawImage(imageDefaultBottom, 0, 0, r.width, r.height, unitRect.x, unitRect.y,
                                unitRect.width, unitRect.height);
                    }
                }
            }
        }

        if (objectToolTipLocation != null)
            drawObjectToolTip(gc);
    }

    /**
     * Draw tooltip for current object
     * 
     * @param gc
     */
    private void drawObjectToolTip(GC gc) {
        gc.setFont(objectToolTipHeaderFont);
        Point titleSize = gc.textExtent(tooltipObject.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;

        List<String> texts = new ArrayList<String>();
        if (tooltipObject instanceof AbstractNode) {
            texts.add(((AbstractNode) tooltipObject).getPrimaryIP().getHostAddress());
            texts.add(((AbstractNode) tooltipObject).getPlatformName());
            String sd = ((AbstractNode) tooltipObject).getSystemDescription();
            if (sd.length() > 127)
                sd = sd.substring(0, 127) + "...";
            texts.add(sd);
            texts.add(((AbstractNode) tooltipObject).getSnmpSysName());
            texts.add(((AbstractNode) tooltipObject).getSnmpSysContact());
        }

        for (String s : texts) {
            if ((s == null) || s.isEmpty())
                continue;

            Point pt = gc.textExtent(s);
            if (width < pt.x)
                width = pt.x;
            height += pt.y;
        }

        List<DciValue> values = ((DataCollectionTarget) tooltipObject).getTooltipDciData();
        if (!values.isEmpty()) {
            for (DciValue v : values) {
                Point pt = gc.textExtent(v.getName() + "  " + v.getValue());
                if (width < pt.x)
                    width = pt.x;
                height += pt.y;
            }
            height += OBJECT_TOOLTIP_SPACING * 2 + 1;
        }

        if ((tooltipObject.getComments() != null) && !tooltipObject.getComments().isEmpty()) {
            Point pt = gc.textExtent(tooltipObject.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(239, 225, 160));
        gc.setAlpha(240);
        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(tooltipObject.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(tooltipObject.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; //$NON-NLS-1$
        y = rect.y + OBJECT_TOOLTIP_Y_MARGIN + titleSize.y + OBJECT_TOOLTIP_SPACING + 2 - textLineHeight;
        for (String s : texts) {
            if ((s == null) || s.isEmpty())
                continue;

            y += textLineHeight;
            gc.drawText(s, rect.x + OBJECT_TOOLTIP_X_MARGIN, y, true);
        }

        if (!values.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));

            for (DciValue v : values) {
                gc.drawText(v.getName(), rect.x + OBJECT_TOOLTIP_X_MARGIN, y, true);
                Point pt = gc.textExtent(v.getValue());
                gc.drawText(v.getValue(), rect.x + rect.width - OBJECT_TOOLTIP_X_MARGIN - pt.x, y, true);
                y += textLineHeight;
            }
            y -= textLineHeight;
        }

        if ((tooltipObject.getComments() != null) && !tooltipObject.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(tooltipObject.getComments(), rect.x + OBJECT_TOOLTIP_X_MARGIN, y, true);
        }

        objectTooltipRectangle = rect;
    }

    /* (non-Javadoc)
     * @see org.eclipse.swt.widgets.Composite#computeSize(int, int, boolean)
     */
    @Override
    public Point computeSize(int wHint, int hHint, boolean changed) {
        if (hHint == SWT.DEFAULT) {
            int borderWidth = FULL_UNIT_WIDTH / BORDER_WIDTH_RATIO;
            return new Point(FULL_UNIT_WIDTH + MARGIN_WIDTH * 2 + UNIT_NUMBER_WIDTH + borderWidth * 2,
                    rack.getHeight() * FULL_UNIT_HEIGHT + MARGIN_HEIGHT * 2 + borderWidth * 2);
        }

        double unitHeight = (double) hHint / (double) rack.getHeight();
        int unitWidth = (int) (unitHeight * UNIT_WH_RATIO);
        int borderWidth = unitWidth / BORDER_WIDTH_RATIO;
        if (borderWidth < 3)
            borderWidth = 3;

        unitWidth = (int) ((double) (hHint - ((borderWidth + 1) / 2) * 2 - MARGIN_HEIGHT * 2)
                / (double) rack.getHeight() * UNIT_WH_RATIO);
        return new Point(unitWidth + MARGIN_WIDTH * 2 + UNIT_NUMBER_WIDTH + borderWidth * 2, hHint);
    }

    /* (non-Javadoc)
     * @see org.eclipse.swt.events.DisposeListener#widgetDisposed(org.eclipse.swt.events.DisposeEvent)
     */
    @Override
    public void widgetDisposed(DisposeEvent e) {
        for (int i = 0; i < labelFonts.length; i++)
            labelFonts[i].dispose();

        objectToolTipHeaderFont.dispose();

        imageDefaultTop.dispose();
        imageDefaultMiddle.dispose();
        imageDefaultBottom.dispose();

        ImageProvider.getInstance().removeUpdateListener(this);
    }

    /* (non-Javadoc)
     * @see org.netxms.ui.eclipse.imagelibrary.shared.ImageUpdateListener#imageUpdated(java.util.UUID)
     */
    @Override
    public void imageUpdated(UUID guid) {
        boolean found = false;
        List<RackElement> units = rack.getUnits();
        for (RackElement e : units) {
            if (guid.equals(e.getRackImage())) {
                found = true;
                break;
            }
        }
        if (found) {
            getDisplay().asyncExec(new Runnable() {
                @Override
                public void run() {
                    redraw();
                }
            });
        }
    }

    /**
     * Get object at given point
     * 
     * @param p
     * @return
     */
    private AbstractObject getObjectAtPoint(Point p) {
        for (ObjectImage i : objects)
            if (i.contains(p)) {
                return (AbstractObject) i.getObject();
            }
        return null;
    }

    /* (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) {
        AbstractObject object = getObjectAtPoint(new Point(e.x, e.y));
        if (objectTooltipRectangle != null) {
            objectToolTipLocation = null;
            objectTooltipRectangle = null;
            tooltipObject = null;
            redraw();
        }
        if ((e.button == 1) && (object != null)) {
            mouseHover(e);
        }
        setCurrentObject(object);
    }

    /* (non-Javadoc)
     * @see org.eclipse.swt.events.MouseListener#mouseUp(org.eclipse.swt.events.MouseEvent)
     */
    @Override
    public void mouseUp(MouseEvent e) {
    }

    /* (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) {
        objectToolTipLocation = null;
        objectTooltipRectangle = null;
        tooltipObject = null;
        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
            return;

        AbstractObject object = getObjectAtPoint(new Point(e.x, e.y));
        if (object != selectedObject) {
            objectToolTipLocation = (object != null) ? new Point(e.x, e.y) : null;
            tooltipObject = object;
            redraw();
        }
    }

    /* (non-Javadoc)
     * @see org.eclipse.swt.events.MouseMoveListener#mouseMove(org.eclipse.swt.events.MouseEvent)
     */
    @Override
    public void mouseMove(MouseEvent e) {
        if ((objectTooltipRectangle != null) && !objectTooltipRectangle.contains(e.x, e.y)) {
            objectTooltipRectangle = null;
            objectToolTipLocation = null;
            tooltipObject = null;
            redraw();
        }
    }

    /**
     * Add selection listener
     * 
     * @param listener
     */
    public void addSelectionListener(RackSelectionListener listener) {
        selectionListeners.add(listener);
    }

    /**
     * Remove selection listener
     * 
     * @param listener
     */
    public void removeSelectionListener(RackSelectionListener listener) {
        selectionListeners.remove(listener);
    }

    /**
     * Set current selection
     * 
     * @param o
     */
    private void setCurrentObject(AbstractObject o) {
        selectedObject = o;
        for (RackSelectionListener l : selectionListeners)
            l.objectSelected(selectedObject);
    }

    /**
     * Object image information
     */
    private class ObjectImage {
        private RackElement object;
        private Rectangle rect;

        public ObjectImage(RackElement object, Rectangle rect) {
            this.object = object;
            this.rect = new Rectangle(rect.x, rect.y, rect.width, rect.height);
        }

        public boolean contains(Point p) {
            return rect.contains(p);
        }

        public RackElement getObject() {
            return object;
        }
    }
}