/*******************************************************************************
* Copyright (c) 2002, 2009 Innoopract Informationssysteme GmbH.
* 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:
* Innoopract Informationssysteme GmbH - initial API and implementation
* EclipseSource - ongoing development
******************************************************************************/
package org.eclipse.swt.browser;
import java.util.*;
import org.eclipse.rwt.internal.service.ContextProvider;
import org.eclipse.rwt.internal.service.IServiceStateInfo;
import org.eclipse.rwt.lifecycle.ProcessActionRunner;
import org.eclipse.rwt.lifecycle.WidgetUtil;
import org.eclipse.swt.*;
import org.eclipse.swt.internal.widgets.IBrowserAdapter;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
/**
* Instances of this class implement the browser user interface
* metaphor. It allows the user to visualize and navigate through
* HTML documents.
* <p>
* Note that although this class is a subclass of <code>Composite</code>,
* it does not make sense to set a layout on it.
* </p><p>
* IMPORTANT: This class is <em>not</em> intended to be subclassed.
* </p>
*
* @since 1.0
*
* <hr/>
* <p>Currently implemented</p>
* <ul><li>text and url property</li></ul>
* <p>The enabled property in not (yet) evaluated.</p>
* <p>Focus events are not yet implemented</p>
*
*/
// TODO [rh] implement refresh method
// TODO [rh] bring focus events to work
public class Browser extends Composite {
private final class BrowserAdapter implements IBrowserAdapter {
public String getText() {
return Browser.this.html;
}
public String getExecuteScript() {
return Browser.this.executeScript;
}
public void setExecuteResult( final boolean result ) {
ProcessActionRunner.add( new Runnable() {
public void run() {
Browser.this.executeResult = Boolean.valueOf( result );
}
} );
}
public BrowserFunction[] getBrowserFunctions() {
return Browser.this.getBrowserFunctions();
}
}
private static final String FUNCTIONS_TO_CREATE
= Browser.class.getName() + "#functionsToCreate.";
private static final String FUNCTIONS_TO_DESTROY
= Browser.class.getName() + "#functionsToDestroy.";
private static final String ABOUT_BLANK = "about:blank";
private String url;
private String html;
public String executeScript;
private Boolean executeResult;
private final IBrowserAdapter browserAdapter;
private List functions;
/**
* Constructs a new instance of this class given its parent
* and a style value describing its behavior and appearance.
* <p>
* The style value is either one of the style constants defined in
* class <code>SWT</code> which is applicable to instances of this
* class, or must be built by <em>bitwise OR</em>'ing together
* (that is, using the <code>int</code> "|" operator) two or more
* of those <code>SWT</code> style constants. The class description
* lists the style constants that are applicable to the class.
* Style bits are also inherited from superclasses.
* </p>
*
* @param parent a widget which will be the parent of the new instance (cannot be null)
* @param style the style of widget to construct
*
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
* </ul>
* @exception SWTError <ul>
* <li>ERROR_NO_HANDLES if a handle could not be obtained for browser creation</li>
* </ul>
*
* @see org.eclipse.swt.Widget#getStyle
*/
public Browser( final Composite parent, final int style ) {
super( parent, style );
html = "";
url = "";
browserAdapter = new BrowserAdapter();
functions = new ArrayList();
}
/**
* Loads a URL.
*
* @param url the URL to be loaded
*
* @return true if the operation was successful and false otherwise.
*
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT - if the url is null</li>
* </ul>
*
* @exception SWTException <ul>
* <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
* <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
* </ul>
*
* @see #getUrl
*/
public boolean setUrl( final String url ) {
checkWidget();
if( url == null ) {
SWT.error( SWT.ERROR_NULL_ARGUMENT );
}
LocationEvent event;
event = new LocationEvent( this, LocationEvent.CHANGING, url );
event.processEvent();
boolean result = event.doit;
if( result ) {
this.url = url;
html = "";
event = new LocationEvent( this, LocationEvent.CHANGED, url );
event.top = true;
event.processEvent();
}
return result;
}
/**
* Returns the current URL.
*
* @return the current URL or an empty <code>String</code> if there is no current URL
*
* @exception SWTException <ul>
* <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
* <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
* </ul>
*
* @see #setUrl
*/
public String getUrl() {
checkWidget();
return url;
}
/**
* Renders HTML.
*
* <p>
* The html parameter is Unicode encoded since it is a java <code>String</code>.
* As a result, the HTML meta tag charset should not be set. The charset is implied
* by the <code>String</code> itself.
*
* @param html the HTML content to be rendered
*
* @return true if the operation was successful and false otherwise.
*
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT - if the html is null</li>
* </ul>
*
* @exception SWTException <ul>
* <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
* <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
* </ul>
*
* @see #setUrl
*/
public boolean setText( final String html ) {
checkWidget();
if( html == null ) {
SWT.error( SWT.ERROR_NULL_ARGUMENT );
}
LocationEvent event;
event = new LocationEvent( this, LocationEvent.CHANGING, ABOUT_BLANK );
event.processEvent();
boolean result = event.doit;
if( result ) {
this.html = html;
url = "";
event = new LocationEvent( this, LocationEvent.CHANGED, ABOUT_BLANK );
event.top = true;
event.processEvent();
}
return result;
}
/**
* Execute the specified script.
*
* <p>Execute a script containing javascript commands in the context of the
* current document.</p>
*
* <!-- Begin RAP specific -->
* <p><strong>Note:</strong> Care should be taken when using this method.
* The given <code>script</code> is executed in an <code>IFRAME</code>
* inside the document that represents the client-side application.
* Since the execution context of an <code>IFRAME</code> is not fully
* isolated from the surrounding documument it may break the client-side
* application.</p>
* <!-- End RAP specific -->
*
* @param script the script with javascript commands
*
* @return <code>true</code> if the operation was successful and
* <code>false</code> otherwise
*
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT - if the script is null</li>
* </ul>
*
* @exception SWTException <ul>
* <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
* <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
* </ul>
*
* @since 1.1
*/
public boolean execute( final String script ) {
checkWidget();
if( script == null ) {
SWT.error( SWT.ERROR_NULL_ARGUMENT );
}
executeScript = script;
executeResult = null;
while( executeResult == null ) {
Display display = getDisplay();
if( !display.readAndDispatch() ) {
display.sleep();
}
}
executeScript = null;
return executeResult.booleanValue();
}
/**
* Adds the listener to the collection of listeners who will be
* notified when the current location has changed or is about to change.
* <p>
* This notification typically occurs when the application navigates
* to a new location with {@link #setUrl(String)} or when the user
* activates a hyperlink.
* </p>
*
* @param listener the listener which should be notified
*
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
* </ul>
*
* @exception SWTException <ul>
* <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
* <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
* </ul>
*/
public void addLocationListener( final LocationListener listener ) {
checkWidget();
LocationEvent.addListener( this, listener );
}
/**
* Removes the listener from the collection of listeners who will
* be notified when the current location is changed or about to be changed.
*
* @param listener the listener which should no longer be notified
*
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
* </ul>
*
* @exception SWTException <ul>
* <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
* <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
* </ul>
*/
public void removeLocationListener( final LocationListener listener ) {
checkWidget();
LocationEvent.removeListener( this, listener );
}
public Object getAdapter( final Class adapter ) {
Object result;
if( IBrowserAdapter.class.equals( adapter ) ) {
result = browserAdapter;
} else {
result = super.getAdapter( adapter );
}
return result;
}
//////////////////////////////////////////
// BrowserFunction support helping methods
private BrowserFunction[] getBrowserFunctions() {
BrowserFunction[] result = new BrowserFunction[ functions.size() ];
for( int i = 0; i < functions.size(); i++ ) {
result[ i ] = ( BrowserFunction )functions.get( i );
}
return result;
}
void createFunction( final BrowserFunction function ) {
boolean removed = false;
for( int i = 0; i < functions.size() && !removed; i++ ) {
BrowserFunction current = ( BrowserFunction )functions.get( i );
if( current.name.equals( function.name ) ) {
functions.remove( current );
removed = true;
}
}
functions.add( function );
if( !removed ) {
updateBrowserFunctions( function.getName(), true );
}
}
void destroyFunction( final BrowserFunction function ) {
functions.remove( function );
updateBrowserFunctions( function.getName(), false );
}
private void updateBrowserFunctions( final String function,
final boolean create )
{
IServiceStateInfo stateInfo = ContextProvider.getStateInfo();
String id = WidgetUtil.getId( this );
String key = create ? FUNCTIONS_TO_CREATE + id : FUNCTIONS_TO_DESTROY + id;
String[] funcList = ( String[] )stateInfo.getAttribute( key );
String[] newList;
if( funcList == null ) {
newList = new String[ 1 ];
newList[ 0 ] = function;
} else {
newList = new String[ funcList.length + 1 ];
System.arraycopy( funcList, 0, newList, 0, funcList.length );
newList[ funcList.length ] = function;
}
stateInfo.setAttribute( key, newList );
}
protected void checkWidget() {
super.checkWidget ();
}
}