/******************************************************************************* * Copyright (c) 2012, 2016 EclipseSource 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: * EclipseSource - initial API and implementation ******************************************************************************/ package com.eclipsesource.tabris.widgets; import static com.eclipsesource.tabris.internal.ClientCanvasOperator.DRAWINGS_PROPERTY; import static com.eclipsesource.tabris.internal.ClientCanvasOperator.DRAWING_EVENT; import static com.eclipsesource.tabris.internal.DataWhitelist.WhiteListEntry.CLIENT_CANVAS; import static org.eclipse.rap.rwt.internal.protocol.ProtocolUtil.readEventPropertyValue; import java.util.ArrayList; import java.util.List; import org.eclipse.rap.json.JsonValue; import org.eclipse.rap.rwt.internal.lifecycle.RemoteAdapter; import org.eclipse.rap.rwt.internal.lifecycle.WidgetUtil; import org.eclipse.rap.rwt.internal.protocol.RemoteObjectFactory; import org.eclipse.swt.events.PaintEvent; import org.eclipse.swt.events.PaintListener; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.internal.widgets.WidgetRemoteAdapter; import org.eclipse.swt.widgets.Canvas; import org.eclipse.swt.widgets.Composite; import com.eclipsesource.tabris.internal.ClientCanvasOperator; import com.eclipsesource.tabris.internal.DrawingsCache; import com.eclipsesource.tabris.internal.GCOperationDispatcher; /** * <i>Provisional API, may change in future versions. Use it at your own risk.</i> * <p> * A <code>ClientCanvas</code> can be used like a SWT <code>Canvas</code> with the difference that a client can draw on * it's area, too. Client side drawing will be sent back to the server and registered <code>ClientDrawListeners</code> * will be notified.</code> * </p> * * @see ClientDrawListener * * @since 0.6 */ @SuppressWarnings("restriction") public class ClientCanvas extends Canvas { private final List<ClientDrawListener> drawListeners; private final DrawingsCache cache; private PaintListener paintListener; public ClientCanvas( Composite parent, int style ) { super( parent, style ); drawListeners = new ArrayList<ClientDrawListener>(); cache = new DrawingsCache(); addDispatchPaintListener(); setData( CLIENT_CANVAS.getKey(), Boolean.TRUE ); exchangesOperationHandler( this ); } private void addDispatchPaintListener() { paintListener = new PaintListener() { @Override public void paintControl( PaintEvent event ) { GC gc = event.gc; processClientDrawings( gc ); gc.drawPoint( -1, -1 ); //TODO: This is a workaround to force updates, see RAP bug 377070 } }; super.addPaintListener( paintListener ); } /** * <p> * Adds a <code>ClientDrawListener</code> that gets called when a client draws. * </p> * * @see ClientDrawListener */ public void addClientDrawListener( ClientDrawListener listener ) { drawListeners.add( listener ); } /** * <p> * Removes a <code>ClientDrawListener</code>. * </p> * * @see ClientDrawListener */ public void removeClientDrawListener( ClientDrawListener listener ) { drawListeners.remove( listener ); } /** * <p> * Clears all client side drawings. * </p> */ public void clear() { if( !isDisposed() ) { cache.clear(); redraw(); fireDrawEvent(); } } /** * <p> * Undo the most recent client side drawing. * </p> */ public void undo() { if( !isDisposed() ) { if( cache.hasUndo() ) { cache.undo(); redraw(); fireDrawEvent(); } } } /** * <p> * returns if a undo can be performed. * </p> */ public boolean hasUndo() { return cache.hasUndo(); } /** * <p> * Redo the most recent undo. * </p> */ public void redo() { if( !isDisposed() ) { if( cache.hasRedo() ) { cache.redo(); redraw(); fireDrawEvent(); } } } /** * <p> * Returns if a redo can be performed. * </p> */ public boolean hasRedo() { return cache.hasRedo(); } @Override public void addPaintListener( PaintListener listener ) { removePaintListener( paintListener ); super.addPaintListener( listener ); super.addPaintListener( paintListener ); } private void processClientDrawings( GC gc ) { JsonValue drawings = readEventPropertyValue( WidgetUtil.getId( this ), DRAWING_EVENT, DRAWINGS_PROPERTY ); if( drawings != null ) { cacheDrawings( drawings.asString() ); cache.clearRemoved(); fireDrawEvent(); } dispatchDrawings( gc ); } private void fireDrawEvent() { if( !isDisposed() ) { List<ClientDrawListener> listeners = new ArrayList<ClientDrawListener>( drawListeners ); for( ClientDrawListener listener : listeners ) { listener.receivedDrawing(); } } } private void cacheDrawings( String drawings ) { cache.cache( drawings ); } private void dispatchDrawings( GC gc ) { for( String drawing : cache.getCachedDrawings() ) { if( drawing != null ) { GCOperationDispatcher dispatcher = new GCOperationDispatcher( gc, drawing ); dispatcher.dispatch(); } } } @SuppressWarnings({ "unchecked" }) @Override public <T> T getAdapter( Class<T> adapter ) { T result = super.getAdapter( adapter ); if( adapter == DrawingsCache.class ) { result = ( T )cache; } else if( adapter == List.class ) { return ( T )drawListeners; } return result; } private void exchangesOperationHandler( final ClientCanvas widget ) { WidgetRemoteAdapter adapter = ( WidgetRemoteAdapter )getAdapter( RemoteAdapter.class ); adapter.addRenderRunnable( new Runnable() { @Override public void run() { ClientCanvasOperator handler = new ClientCanvasOperator( widget ); RemoteObjectFactory.getRemoteObject( widget ).setHandler( handler ); } } ); } }