/******************************************************************************* * Copyright (c) 2002, 2010 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.internal.widgets.displaykit; import java.io.IOException; import java.text.MessageFormat; import java.util.HashMap; import java.util.Map; import java.util.logging.Level; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.rwt.branding.AbstractBranding; import org.eclipse.rwt.internal.RWTMessages; import org.eclipse.rwt.internal.branding.BrandingUtil; import org.eclipse.rwt.internal.lifecycle.*; import org.eclipse.rwt.internal.resources.ResourceRegistry; import org.eclipse.rwt.internal.service.*; import org.eclipse.rwt.internal.theme.*; import org.eclipse.rwt.internal.util.HTML; import org.eclipse.rwt.lifecycle.*; import org.eclipse.rwt.resources.IResource; import org.eclipse.swt.dnd.DragSource; import org.eclipse.swt.dnd.DropTarget; import org.eclipse.swt.events.TypedEvent; import org.eclipse.swt.graphics.Device; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.internal.graphics.TextSizeDeterminationFacadeImpl; import org.eclipse.swt.internal.widgets.*; import org.eclipse.swt.internal.widgets.WidgetTreeVisitor.AllWidgetTreeVisitor; import org.eclipse.swt.internal.widgets.shellkit.ShellLCA; import org.eclipse.swt.widgets.*; public class DisplayLCA implements IDisplayLifeCycleAdapter { private final static String PATTERN_APP_STARTUP = "var req = org.eclipse.swt.Request.getInstance();" + "req.setUrl( \"{0}\" );" + "req.setUIRootId( \"{1}\" );" + "var app = new org.eclipse.swt.Application();" + "qx.core.Init.getInstance().setApplication( app );"; private final static String PATTERN_REQUEST_COUNTER = "var req = org.eclipse.swt.Request.getInstance();" + "req.setRequestCounter( \"{0,number,#}\" );"; private static final String CLIENT_LOG_LEVEL = "org.eclipse.rwt.clientLogLevel"; // Maps Java Level to the closest qooxdoo log level private static final Map LOG_LEVEL_MAP = new HashMap( 8 + 1, 1f ); static final String PROP_FOCUS_CONTROL = "focusControl"; static final String PROP_CURR_THEME = "currTheme"; static final String PROP_EXIT_CONFIRMATION = "exitConfirmation"; static final String PROP_TIMEOUT_PAGE = "timeoutPage"; private static final class RenderVisitor extends AllWidgetTreeVisitor { private IOException ioProblem; public boolean doVisit( final Widget widget ) { ioProblem = null; boolean result = true; try { render( widget ); runRenderRunnable( widget ); } catch( final IOException ioe ) { ioProblem = ioe; result = false; } return result; } private void reThrowProblem() throws IOException { if( ioProblem != null ) { throw ioProblem; } } private static void render( final Widget widget ) throws IOException { WidgetUtil.getLCA( widget ).render( widget ); } private static void runRenderRunnable( final Widget widget ) throws IOException { WidgetAdapter adapter = ( WidgetAdapter )WidgetUtil.getAdapter( widget ); if( adapter.getRenderRunnable() != null ) { adapter.getRenderRunnable().afterRender(); adapter.clearRenderRunnable(); } } } static { // Available qooxdoo log level: // LEVEL_OFF, LEVEL_ALL, // LEVEL_DEBUG, LEVEL_INFO, LEVEL_WARN, LEVEL_ERROR, LEVEL_FATAL LOG_LEVEL_MAP.put( Level.OFF, "qx.log.Logger.LEVEL_OFF" ); LOG_LEVEL_MAP.put( Level.ALL, "qx.log.Logger.LEVEL_ALL" ); LOG_LEVEL_MAP.put( Level.WARNING, "qx.log.Logger.LEVEL_WARN" ); LOG_LEVEL_MAP.put( Level.INFO, "qx.log.Logger.LEVEL_INFO" ); LOG_LEVEL_MAP.put( Level.SEVERE, "qx.log.Logger.LEVEL_ERROR" ); LOG_LEVEL_MAP.put( Level.FINE, "qx.log.Logger.LEVEL_DEBUG" ); LOG_LEVEL_MAP.put( Level.FINER, "qx.log.Logger.LEVEL_DEBUG" ); LOG_LEVEL_MAP.put( Level.FINEST, "qx.log.Logger.LEVEL_DEBUG" ); } //////////////////////////////////////////////////////// // interface implementation of IDisplayLifeCycleAdapter public void preserveValues( final Display display ) { IWidgetAdapter adapter = DisplayUtil.getAdapter( display ); adapter.preserve( PROP_FOCUS_CONTROL, display.getFocusControl() ); adapter.preserve( PROP_CURR_THEME, ThemeUtil.getCurrentThemeId() ); adapter.preserve( PROP_TIMEOUT_PAGE, getTimeoutPage() ); adapter.preserve( PROP_EXIT_CONFIRMATION, getExitConfirmation() ); } public void render( final Display display ) throws IOException { HttpServletRequest request = ContextProvider.getRequest(); // TODO [rh] should be replaced by requestCounter == 0 if( request.getParameter( RequestParams.UIROOT ) == null ) { writeClientDocument( display ); } else { HttpServletResponse response = ContextProvider.getResponse(); response.setContentType( HTML.CONTENT_TEXT_JAVASCRIPT_UTF_8 ); disposeWidgets(); writeRequestCounter(); writeTheme( display ); writeErrorPages( display ); writeExitConfirmation( display ); renderShells( display ); writeActiveControls( display ); writeFocus( display ); writeUICallBackActivation( display ); markInitialized( display ); } } private static void renderShells( final Display display ) throws IOException { RenderVisitor visitor = new RenderVisitor(); Composite[] shells = getShells( display ); for( int i = 0; i < shells.length; i++ ) { WidgetTreeVisitor.accept( shells[ i ], visitor ); visitor.reThrowProblem(); } } private static void writeRequestCounter() throws IOException { IServiceStateInfo stateInfo = ContextProvider.getStateInfo(); HtmlResponseWriter out = stateInfo.getResponseWriter(); Object[] args = new Object[] { RWTRequestVersionControl.nextRequestId() }; out.write( MessageFormat.format( PATTERN_REQUEST_COUNTER, args ) ); } private static void writeTheme( final Display display ) throws IOException { String currThemeId = ThemeUtil.getCurrentThemeId(); IWidgetAdapter adapter = DisplayUtil.getAdapter( display ); Object oldThemeId = adapter.getPreserved( PROP_CURR_THEME ); if( !currThemeId.equals( oldThemeId ) ) { Theme theme = ThemeManager.getInstance().getTheme( currThemeId ); StringBuffer buffer = new StringBuffer(); buffer.append( "qx.theme.manager.Meta.getInstance().setTheme( " ); buffer.append( theme.getJsId() ); buffer.append( " );" ); IServiceStateInfo stateInfo = ContextProvider.getStateInfo(); HtmlResponseWriter out = stateInfo.getResponseWriter(); out.write( buffer.toString() ); } } private static void writeErrorPages( final Display display ) throws IOException { String timeoutPage = getTimeoutPage(); IWidgetAdapter adapter = DisplayUtil.getAdapter( display ); Object oldTimeoutPage = adapter.getPreserved( PROP_TIMEOUT_PAGE ); if( !timeoutPage.equals( oldTimeoutPage ) ) { String pattern = "org.eclipse.swt.Request.getInstance().setTimeoutPage( \"{0}\" );"; Object[] param = new Object[] { timeoutPage }; String jsCode = MessageFormat.format( pattern, param ); IServiceStateInfo stateInfo = ContextProvider.getStateInfo(); HtmlResponseWriter out = stateInfo.getResponseWriter(); out.write( jsCode ); } } private static String getTimeoutPage() { String timeoutTitle = RWTMessages.getMessage( "RWT_SessionTimeoutPageTitle" ); String timeoutHeadline = RWTMessages.getMessage( "RWT_SessionTimeoutPageHeadline" ); String pattern = RWTMessages.getMessage( "RWT_SessionTimeoutPageMessage" ); Object[] arguments = new Object[]{ "<a {HREF_URL}>", "</a>" }; String timeoutMessage = MessageFormat.format( pattern, arguments ); // TODO Escape umlauts etc String timeoutPage = "<html><head><title>" + timeoutTitle + "</title></head><body><p>" + timeoutHeadline + "</p><p>" + timeoutMessage + "</p></body></html>"; return timeoutPage; } private static void writeExitConfirmation( final Display display ) throws IOException { String exitConfirmation = getExitConfirmation(); IWidgetAdapter adapter = DisplayUtil.getAdapter( display ); Object oldExitConfirmation = adapter.getPreserved( PROP_EXIT_CONFIRMATION ); boolean hasChanged = exitConfirmation == null ? oldExitConfirmation != null : !exitConfirmation.equals( oldExitConfirmation ); if( hasChanged ) { String exitConfirmationStr = exitConfirmation == null ? "null" : "\"" + exitConfirmation + "\""; String code = "qx.core.Init.getInstance().getApplication()" + ".setExitConfirmation( " + exitConfirmationStr + " );"; IServiceStateInfo stateInfo = ContextProvider.getStateInfo(); HtmlResponseWriter out = stateInfo.getResponseWriter(); out.write( code ); } } private static String getExitConfirmation() { AbstractBranding branding = BrandingUtil.findBranding(); String result = null; // does not display exit dialog if( branding.showExitConfirmation() ) { result = branding.getExitConfirmationText(); if( result == null ) { result = ""; // displays an exit dialog with empty message } } return result; } private static void writeClientDocument( final Display display ) throws IOException { HttpServletResponse response = ContextProvider.getResponse(); response.setContentType( HTML.CONTENT_TEXT_HTML_UTF_8 ); IServiceStateInfo stateInfo = ContextProvider.getStateInfo(); HtmlResponseWriter out = stateInfo.getResponseWriter(); out.startElement( HTML.HTML, null ); out.startElement( HTML.HEAD, null ); out.startElement( HTML.META, null ); out.writeAttribute( HTML.HTTP_EQUIV, HTML.CONTENT_TYPE, null ); out.writeAttribute( HTML.CONTENT, HTML.CONTENT_TEXT_HTML_UTF_8, null ); out.startElement( HTML.TITLE, null ); out.endElement( HTML.TITLE ); writeLibraries(); out.endElement( HTML.HEAD ); out.startElement( HTML.BODY, null ); IWidgetAdapter adapter = DisplayUtil.getAdapter( display ); String id = adapter.getId(); out.startElement( HTML.SCRIPT, null ); out.writeAttribute( HTML.TYPE, HTML.CONTENT_TEXT_JAVASCRIPT, null ); writeAppScript( id ); writeErrorPages( display ); writeExitConfirmation( display ); out.endElement( HTML.SCRIPT ); out.endElement( HTML.BODY ); out.endElement( HTML.HTML ); } public static void writeAppScript( final String id ) throws IOException { StringBuffer initScript = new StringBuffer(); initScript.append( jsConfigureLogger( getClientLogLevel() ) ); initScript.append( jsAppInitialization( id ) ); HtmlResponseWriter out = ContextProvider.getStateInfo().getResponseWriter(); out.writeText( initScript.toString(), null ); } public static void writeLibraries() throws IOException { AdlibResourcesUtil.registerResources(); ThemeManager.getInstance().registerResources(); writeScrollBarStyle(); writeJSLibraries(); } private static void writeJSLibraries() throws IOException { HtmlResponseWriter out = ContextProvider.getStateInfo().getResponseWriter(); IResource[] resources = ResourceRegistry.get(); for( int i = 0; i < resources.length; i++ ) { if( resources[ i ].isExternal() && resources[ i ].isJSLibrary() ) { writeScriptTag( out, resources[ i ].getLocation() ); } } writeScriptTag( out, JSLibraryServiceHandler.getRequestURL() ); } public void readData( final Display display ) { readBounds( display ); readCursorLocation( display ); readFocusControl( display ); WidgetTreeVisitor visitor = new AllWidgetTreeVisitor() { public boolean doVisit( final Widget widget ) { IWidgetLifeCycleAdapter adapter = WidgetUtil.getLCA( widget ); adapter.readData( widget ); return true; } }; Shell[] shells = getShells( display ); for( int i = 0; i < shells.length; i++ ) { Composite shell = shells[ i ]; WidgetTreeVisitor.accept( shell, visitor ); } for( int i = 0; i < shells.length; i++ ) { if( shells[ i ].getMaximized() || shells[ i ].getFullScreen() ) { Object adapter = shells[ i ].getAdapter( IShellAdapter.class ); IShellAdapter shellAdapter = ( IShellAdapter )adapter; shellAdapter.setBounds( display.getBounds() ); } } DNDSupport.processEvents(); } public void processAction( final Device display ) { ProcessActionRunner.execute(); TypedEvent.processScheduledEvents(); } ///////////////////////////// // Helping methods for render private static String jsConfigureLogger( final Level level ) { String jsLevel = ( String )LOG_LEVEL_MAP.get( level ); String code = "qx.log.Logger.ROOT_LOGGER.setMinLevel( {0} );"; return MessageFormat.format( code, new Object[] { jsLevel } ); } private static String jsAppInitialization( final String displayId ) { StringBuffer code = new StringBuffer(); // font size measurment code.append( TextSizeDeterminationFacadeImpl.getStartupProbeCode() ); // application HttpServletRequest request = ContextProvider.getRequest(); String url = request.getServletPath().substring( 1 ); Object[] param = new Object[] { ContextProvider.getResponse().encodeURL( url ), displayId }; code.append( MessageFormat.format( PATTERN_APP_STARTUP, param ) ); return code.toString(); } private static void disposeWidgets() throws IOException { Widget[] disposedWidgets = DisposedWidgets.getAll(); // TODO [rh] get rid of dependency on DragSource/DropTarget // Must dispose of DragSources and DropTargets first for( int i = disposedWidgets.length - 1; i >= 0; i-- ) { Widget toDispose = disposedWidgets[ i ]; if( toDispose instanceof DragSource || toDispose instanceof DropTarget ) { AbstractWidgetLCA lca = WidgetUtil.getLCA( toDispose ); lca.renderDispose( toDispose ); } } // TODO [rst] since widget pooling is removed, the loop should be reverted // again // [fappel]: client side disposal order is crucial for the widget // caching mechanism - we need to dispose of children first. This // is reverse to the server side mechanism (which is analog to // SWT). for( int i = disposedWidgets.length - 1; i >= 0; i-- ) { Widget toDispose = disposedWidgets[ i ]; if( !( toDispose instanceof DragSource ) && !( toDispose instanceof DropTarget ) ) { AbstractWidgetLCA lca = WidgetUtil.getLCA( toDispose ); lca.renderDispose( toDispose ); } } } private static void writeScriptTag( final HtmlResponseWriter out, final String library ) throws IOException { out.startElement( HTML.SCRIPT, null ); out.writeAttribute( HTML.TYPE, HTML.CONTENT_TEXT_JAVASCRIPT, null ); out.writeAttribute( HTML.SRC, library, null ); out.writeAttribute( HTML.CHARSET, HTML.CHARSET_NAME_UTF_8, null ); out.endElement( HTML.SCRIPT ); } private static void writeScrollBarStyle() throws IOException { // TODO [bm] this could be part of the themeing or? HtmlResponseWriter out = ContextProvider.getStateInfo().getResponseWriter(); // Changing the scrollbar style is only supported by IE out.write( "<!--[if IE]>" ); out.startElement( "style", out ); out.writeAttribute( "type", "text/css", null ); StringBuffer css = new StringBuffer(); css.append( "html, body, iframe { " ); css.append( "scrollbar-base-color:#c0c0c0;" ); css.append( "scrollbar-3d-light-color:#f8f8ff;" ); css.append( "scrollbar-arrow-color:#0080c0;" ); css.append( "scrollbar-darkshadow-color:#f0f0f8;" ); css.append( "scrollbar-face-color:#f8f8ff;" ); css.append( "scrollbar-highlight-color:white;" ); css.append( "scrollbar-shadow-color:gray;" ); css.append( "scrollbar-track-color:#f0f0f8;" ); css.append( "}" ); out.write( css.toString() ); out.endElement( "style" ); out.write( "<![endif]-->" ); } private static void writeFocus( final Display display ) throws IOException { if( !display.isDisposed() ) { IDisplayAdapter displayAdapter = getDisplayAdapter( display ); IWidgetAdapter widgetAdapter = DisplayUtil.getAdapter( display ); Object oldValue = widgetAdapter.getPreserved( PROP_FOCUS_CONTROL ); if( !widgetAdapter.isInitialized() || oldValue != display.getFocusControl() || displayAdapter.isFocusInvalidated() ) { // TODO [rst] Added null check as a NPE occurred in some rare cases Control focusControl = display.getFocusControl(); if( focusControl != null ) { // TODO [rh] use JSWriter to output focus JavaScript IServiceStateInfo stateInfo = ContextProvider.getStateInfo(); HtmlResponseWriter out = stateInfo.getResponseWriter(); String id = WidgetUtil.getId( display.getFocusControl() ); out.write( "org.eclipse.swt.WidgetManager.getInstance()." ); out.write( "focus( \"" ); out.write( id ); out.write( "\" );" ); } } } } private static void writeUICallBackActivation( final Display display ) throws IOException { if( !display.isDisposed() ) { UICallBackServiceHandler.writeActivation(); } } // TODO [rh] writing activeControl should be handled by the ShellLCA itself // The reason why this is currently done here is, that the control to // activate might not yet be created client-side, when ShellLCA writes // the statement to set the active control. private static void writeActiveControls( final Display display ) throws IOException { Shell[] shells = getShells( display ); for( int i = 0; i < shells.length; i++ ) { ShellLCA.writeActiveControl( shells[ i ] ); } } private static void markInitialized( final Display display ) { WidgetAdapter adapter = ( WidgetAdapter )DisplayUtil.getAdapter( display ); adapter.setInitialized( true ); } private static Level getClientLogLevel() { Level result = Level.OFF; String logLevel = System.getProperty( CLIENT_LOG_LEVEL ); if( logLevel != null ) { logLevel = logLevel.toUpperCase(); Level[] knownLogLevels = new Level[ LOG_LEVEL_MAP.size() ]; LOG_LEVEL_MAP.keySet().toArray( knownLogLevels ); for( int i = 0; i < knownLogLevels.length; i++ ) { if( knownLogLevels[ i ].getName().equals( logLevel ) ) { result = knownLogLevels[ i ]; } } } return result; } static void readBounds( final Display display ) { Rectangle oldBounds = display.getBounds(); int width = readIntPropertyValue( display, "bounds.width", oldBounds.width ); int height = readIntPropertyValue( display, "bounds.height", oldBounds.height ); Rectangle bounds = new Rectangle( 0, 0, width, height ); getDisplayAdapter( display ).setBounds( bounds ); } private static void readCursorLocation( final Display display ) { int x = readIntPropertyValue( display, "cursorLocation.x", 0 ); int y = readIntPropertyValue( display, "cursorLocation.y", 0 ); getDisplayAdapter( display ).setCursorLocation( x, y ); } static void readFocusControl( final Display display ) { // TODO [rh] revise this: traversing the widget tree once more only to find // out which control is focused. Could that be optimized? HttpServletRequest request = ContextProvider.getRequest(); StringBuffer focusControlParam = new StringBuffer(); focusControlParam.append( DisplayUtil.getId( display ) ); focusControlParam.append( ".focusControl" ); String id = request.getParameter( focusControlParam.toString() ); if( id != null ) { Control focusControl = null; // Even though the loop below would anyway result in focusControl == null // the client may send 'null' to indicate that no control on the active // shell currently has the input focus. if( !"null".equals( id ) ) { Shell[] shells = getDisplayAdapter( display ).getShells(); for( int i = 0; focusControl == null && i < shells.length; i++ ) { Widget widget = WidgetUtil.find( shells[ i ], id ); if( widget instanceof Control ) { focusControl = ( Control )widget; } } } if( focusControl != null && EventUtil.isAccessible( focusControl ) ) { getDisplayAdapter( display ).setFocusControl( focusControl ); } } } private static String readPropertyValue( final Display display, final String propertyName ) { HttpServletRequest request = ContextProvider.getRequest(); StringBuffer key = new StringBuffer(); key.append( DisplayUtil.getId( display ) ); key.append( "." ); key.append( propertyName ); return request.getParameter( key.toString() ); } private static int readIntPropertyValue( final Display display, final String propertyName, final int defaultValue ) { String value = readPropertyValue( display, propertyName ); int result; if( value == null ) { result = defaultValue; } else { result = Integer.parseInt( value ); } return result; } private static IDisplayAdapter getDisplayAdapter( final Display display ) { Object adapter = display.getAdapter( IDisplayAdapter.class ); return ( IDisplayAdapter )adapter; } private static Shell[] getShells( final Display display ) { return getDisplayAdapter( display ).getShells(); } }