Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to draw over child elements of a Composite in SWT?

Tags:

java

swt

drawing

I understand to draw on a Composite, you can add a paint listener, but that results in drawing under the children. What if I want to draw over the top of children?

The following draws a line, but subc is drawn over it.

Composite c = new Composite(shell, 0);
c.setBackground(shell.getDisplay().getSystemColor(SWT.COLOR_BLUE));
c.setBounds(100, 100, 800, 600);

c.addPaintListener(new PaintListener() {
    public void paintControl(PaintEvent e) {
        e.gc.drawLine(0, 0, 500, 1000);
    }
});

final Composite subc = new Composite(c, 0);
subc.setLayout(null);
subc.setBounds(10, 10, 600, 400);
like image 853
mentics Avatar asked Jul 27 '11 18:07

mentics


1 Answers

It is not all that difficult to solve :-)

You need to add your SWT.Paint listener not just to the Composite, but also to all the contained children (recursively). The trick is then to map the coordinates appropriately for each control...

To illustrate, I have attached some code I use in a number of projects. And yes, I do know that there are missing classes, but you can probably get the idea from this.

/*******************************************************************************
 * Copyright (c) 2007, 2011 The RCP Company and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     The RCP Company - initial API and implementation
 *******************************************************************************/
package com.rcpcompany.uibindings.utils;

import org.eclipse.jface.fieldassist.ControlDecoration;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;

import com.rcpcompany.uibindings.IDisposable;
import com.rcpcompany.uibindings.internal.utils.PaintDecorationManager;

/**
 * Support for arbitrary decorations for {@link Control controls}.
 * <p>
 * <p>
 * Differs from {@link ControlDecoration} in a number of ways:
 * <ul>
 * <li>Support for cells in tables</li>
 * <li>Vastly more efficient when there are many decorations</li>
 * </ul>
 * 
 * @author Tonny Madsen, The RCP Company
 */
public interface IPaintDecoration {
    /**
     * Factory for {@link IPaintDecoration}.
     */
    final class Factory {
        private Factory() {
        }

        /**
         * Adds a new decoration.
         * <p>
         * The decoration is only added if the control of the decoration is non-<code>null</code>.
         * <p>
         * If the decoration supports the {@link IDisposable} interface, it will be notified when
         * the decoration is no longer in use - e.g. when the decoration is removed with
         * {@link #removeDecoration(IPaintDecoration)} or if the control is disposed.
         * 
         * @param decoration the new decoration
         */
        public static void addDecoration(IPaintDecoration decoration) {
            PaintDecorationManager.addDecoration(decoration);
        }

        public static IPaintDecoration paintRectangle(final Control c, Rectangle rect, final Color color) {
            final Rectangle r;
            if (rect == null) {
                final Point s = c.getSize();
                r = new Rectangle(0, 0, s.x, s.y);
            } else {
                r = rect;
            }
            final IPaintDecoration pd = new IPaintDecoration() {

                @Override
                public void paint(Event event, Rectangle area) {
                    // LogUtils.debug(this, event.widget + ": clip=" + event.gc.getClipping() +
                    // " area=" + area);
                    final Color oldForeground = event.gc.getForeground();
                    event.gc.setForeground(color);
                    event.gc.drawRectangle(area.x, area.y, area.width - 1, area.height - 1);
                    event.gc.setForeground(oldForeground);
                }

                @Override
                public Control getControl() {
                    return c;
                }

                @Override
                public Rectangle getArea() {
                    return r;
                }
            };
            addDecoration(pd);

            return pd;
        }

        /**
         * Removes an existing decoration.
         * 
         * @param decoration the decoration to remove
         */
        public static void removeDecoration(IPaintDecoration decoration) {
            PaintDecorationManager.removeDecoration(decoration);
        }
    };

    /**
     * The control of this decoration.
     * <p>
     * The control of a specific decoration may not change during the lifetime of the decoration.
     * 
     * @return the control
     */
    Control getControl();

    /**
     * Returns the area of this decoration in relation to the control.
     * 
     * @return the relative location
     */
    Rectangle getArea();

    /**
     * Paints the decoration.
     * 
     * @param area TODO
     */
    void paint(Event event, Rectangle area);
}

and

/*******************************************************************************
 * Copyright (c) 2007, 2011 The RCP Company and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     The RCP Company - initial API and implementation
 *******************************************************************************/
package com.rcpcompany.uibindings.internal.utils;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.eclipse.jface.util.Util;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;

import com.rcpcompany.uibindings.IDisposable;
import com.rcpcompany.uibindings.internal.Activator;
import com.rcpcompany.uibindings.utils.IPaintDecoration;
import com.rcpcompany.utils.logging.LogUtils;

/**
 * Simple manager that can draw arbitrary "stuff".
 * <p>
 * A manager exists for each {@link Shell} of the application and is automatically disposed with the
 * shell.
 * <p>
 * Each decoration of the manager is handled internally via an {@link DecorationData} object.
 * 
 * @author Tonny Madsen, The RCP Company
 */
public final class PaintDecorationManager implements IDisposable, Listener {

    /**
     * The shell of this manager.
     */
    private final Shell myShell;

    /**
     * Cached platform flag for dealing with platform-specific issue:
     * https://bugs.eclipse.org/bugs/show_bug.cgi?id=219326 : Shell with custom region and
     * SWT.NO_TRIM still has border
     */
    private static boolean MAC = Util.isMac();

    /**
     * Constructs and returns a new manager.
     * 
     * @param shell the shell of the manager
     */
    private PaintDecorationManager(Shell shell) {
        myShell = shell;
        theManagers.put(getShell(), this);
        hookControl(getShell());
    }

    @Override
    public void dispose() {
        /*
         * Unhook all controls. This is automatically remove all decorations.
         */
        for (final Control c : myHookedControls.toArray(new Control[myHookedControls.size()])) {
            unhookControl(c);
        }
        theManagers.remove(getShell());
    }

    public static void addDecoration(IPaintDecoration decoration) {
        final PaintDecorationManager mng = getManager(decoration);
        if (mng != null) {
            mng.addADecoration(decoration);
        }
    }

    public static void removeDecoration(IPaintDecoration decoration) {
        final PaintDecorationManager mng = getManager(decoration);
        if (mng != null) {
            mng.removeADecoration(decoration);
        }
    }

    /**
     * Mapping of all decorations of this manager to internal data for the same decoration.
     */
    private final Map<IPaintDecoration, DecorationData> myDecorations = new HashMap<IPaintDecoration, DecorationData>();

    public void addADecoration(IPaintDecoration decoration) {
        DecorationData dd = myDecorations.get(decoration);
        if (dd == null) {
            dd = new DecorationData(decoration);
        }
        dd.update();
    }

    public void removeADecoration(IPaintDecoration decoration) {
        if (Activator.getDefault().TRACE_CONTROL_DECORATIONS) {
            LogUtils.debug(this, "control: " + decoration.getControl() + "@" + decoration.getControl().hashCode() + "/"
                    + decoration.getArea());
        }
        final DecorationData dd = myDecorations.get(decoration);
        if (dd == null) return;
        dd.dispose();
    }

    /**
     * Map with all defined managers indexed by the shell.
     */
    private static Map<Shell, PaintDecorationManager> theManagers = new HashMap<Shell, PaintDecorationManager>();

    /**
     * Returns the shell of the manager.
     * 
     * @return the shell
     */
    private Shell getShell() {
        return myShell;
    }

    /**
     * Returns the manager for the specified decoration.
     * <p>
     * Creates a new manager if none exists
     * 
     * @param decoration the decoration
     * @return the manager for the shell of the decoration
     */
    private static PaintDecorationManager getManager(IPaintDecoration decoration) {
        final Control c = decoration.getControl();
        if (c == null) return null;
        final Shell shell = c.getShell();
        PaintDecorationManager mng = theManagers.get(shell);
        if (mng == null) {
            mng = new PaintDecorationManager(shell);
        }

        return mng;
    }

    /**
     * The hooked controls of this manager.
     * <p>
     * A control is hooked when first referred in a decoration or a parent...
     * <p>
     * It is not unhooked until the control or this manager is disposed.
     */
    private final Set<Control> myHookedControls = new HashSet<Control>();

    /**
     * Hooks the specified control into this manager.
     * <p>
     * Also hooks all parent controls.
     * 
     * @param control the control
     */
    public void hookControl(Control control) {
        if (myHookedControls.contains(control)) return;

        myHookedControls.add(control);
        control.addListener(SWT.Dispose, this);
        control.addListener(SWT.Paint, this);

        if (control != getShell()) {
            hookControl(control.getParent());
        }
    }

    /**
     * Unhooks a specific control from the manager.
     * 
     * @param control the control
     */
    public void unhookControl(Control control) {
        if (!myHookedControls.contains(control)) return;
        myHookedControls.remove(control);
        if (!control.isDisposed()) {
            control.removeListener(SWT.Dispose, this);
            control.removeListener(SWT.Paint, this);
        }
        for (final DecorationData dd : myDecorations.values()
                .toArray(new DecorationData[myDecorations.values().size()])) {
            if (dd.getControl() == control) {
                dd.dispose();
            }
        }
    }

    @Override
    public void handleEvent(Event event) {
        // LogUtils.debug(this, ToStringUtils.toString(event));
        switch (event.type) {
        case SWT.Dispose:
            handleDispose(event);
            break;
        case SWT.Paint:
            handlePaint(event);
            break;
        default:
            break;
        }
    }

    /**
     * Handles the dispose event.
     * 
     * @param event the event
     */
    private void handleDispose(Event event) {
        if (event.widget == getShell()) {
            dispose();
            return;
        }
        unhookControl((Control) event.widget);
    }

    /**
     * Handles the paint event.
     * 
     * @param event the event
     */
    private void handlePaint(Event event) {
        final Control c = (Control) event.widget;
        final Display display = c.getDisplay();
        final Rectangle area = display.map(c, null, event.x, event.y, event.width, event.height);
        for (final DecorationData dd : myDecorations.values()) {
            if (dd.intersects(area)) {
                dd.paint(event);
            }
        }
    }

    /**
     * Manager internal decoration data for one decoration.
     */
    protected class DecorationData implements IDisposable {

        private final IPaintDecoration myDecoration;

        /**
         * The previous area painted by this decoration relative to the display.
         */
        private Rectangle myPreviousArea = null;

        /**
         * Set to true when the decoration is disposed
         */
        private boolean isDisposed = false;

        /**
         * Constructs and returns a new decoration data object
         * 
         * @param decoration he base decoration
         */
        protected DecorationData(IPaintDecoration decoration) {
            myDecoration = decoration;
            myDecorations.put(getDecoration(), this);
            if (Activator.getDefault().TRACE_CONTROL_DECORATIONS) {
                LogUtils.debug(this, "control: " + this);
            }

            getManager().hookControl(getDecoration().getControl());
        }

        /**
         * Returns the control of the decoration
         * 
         * @return the control
         */
        public Control getControl() {
            return getDecoration().getControl();
        }

        @Override
        public void dispose() {
            isDisposed = true;
            update();
            myDecorations.remove(getDecoration());
            if (Activator.getDefault().TRACE_CONTROL_DECORATIONS) {
                LogUtils.debug(this, "control: " + this);
            }
        }

        /**
         * Returns the manager of this decoration
         * 
         * @return the manager
         */
        public PaintDecorationManager getManager() {
            return PaintDecorationManager.this;
        }

        /**
         * Returns the decoration itself
         * 
         * @return the decoration
         */
        public IPaintDecoration getDecoration() {
            return myDecoration;
        }

        /**
         * Updates this decoration
         */
        public void update() {
            if (Activator.getDefault().TRACE_CONTROL_DECORATIONS) {
                LogUtils.debug(this, "control: " + this);
            }
            /*
             * Calculate new area
             */
            final Rectangle newArea = getDecorationRectangle(getShell());
            /*
             * Compare with last area and image
             */
            if ((newArea == null ? myPreviousArea == null : newArea.equals(myPreviousArea))) {
                if (Activator.getDefault().TRACE_CONTROL_DECORATIONS_VERBOSE) {
                    LogUtils.debug(this, "-- return");
                }
                return;
            }
            Rectangle r = null;
            if (myPreviousArea != null) {
                r = myPreviousArea;
                if (newArea != null) {
                    r.add(newArea);
                }
            } else {
                r = newArea;
            }
            myPreviousArea = newArea;
            if (r != null) {
                // LogUtils.debug(this, "redraw: " + r);
                getShell().redraw(r.x, r.y, r.width, r.height, true);
                if (Activator.getDefault().TRACE_CONTROL_DECORATIONS_VERBOSE) {
                    LogUtils.debug(this, "redraw " + r);
                }
            }
        }

        /**
         * Calculates the area taken by the image translated to a specified target control
         * 
         * @param c the target control or null for the Display
         * 
         * @return the {@link Rectangle} for the image or <code>null</code> if no image is specified
         */
        private Rectangle getDecorationRectangle(Control c) {
            final Control control = getDecoration().getControl();
            final Rectangle b = getDecoration().getArea();
            final Rectangle bounds = new Rectangle(b.x, b.y, b.width + 1, b.height + 1);

            return getShell().getDisplay().map(control, c, bounds);
        }

        /**
         * Paints this decoration.
         * 
         * @param event the {@link SWT#Paint} event
         */
        public void paint(Event event) {
            if (Activator.getDefault().TRACE_CONTROL_DECORATIONS_VERBOSE) {
                LogUtils.debug(this, "paint: " + event.widget);
            }
            // if (!shouldShowDecoration()) {
            // return;
            // }
            final Rectangle rect = getDecorationRectangle((Control) event.widget);
            if (Activator.getDefault().TRACE_CONTROL_DECORATIONS_VERBOSE) {
                LogUtils.debug(this, "paint: " + event.widget + "/" + event.widget.hashCode() + ": rect=" + rect);
            }

            getDecoration().paint(event, rect);
        }

        /**
         * Returns whether this decoration intersects with specified rectangle.
         * 
         * @param eventArea the area to check
         * @return <code>true</code> if the decoration is visible and the area intersects
         */
        public boolean intersects(Rectangle eventArea) {
            if (isDisposed) return false;
            if (!getControl().isVisible()) return false;
            final Rectangle area = getDecorationRectangle(null);
            if (area == null) return false;
            if (!area.intersects(eventArea)) return false;

            return true;
        }

        @Override
        public String toString() {
            return getControl() + "@" + getControl().hashCode() + " " + getDecoration().getArea() + " area "
                    + getDecorationRectangle(null);
        }
    }
}
like image 193
Tonny Madsen Avatar answered Nov 06 '22 05:11

Tonny Madsen