--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal;
+
+import com.itmill.toolkit.Application;
+
+/**
+ * This interface must be implemented by classes wishing to provide Application
+ * resources.
+ * <p>
+ * <code>ApplicationResource</code> are a set of named resources (pictures,
+ * sounds, etc) associated with some specific application. Having named
+ * application resources provides a convenient method for having inter-theme
+ * common resources for an application.
+ * </p>
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+public interface ApplicationResource extends Resource {
+
+ /**
+ * Default cache time.
+ */
+ public static final long DEFAULT_CACHETIME = 1000 * 60 * 60 * 24;
+
+ /**
+ * Gets resource as stream.
+ */
+ public DownloadStream getStream();
+
+ /**
+ * Gets the application of the resource.
+ */
+ public Application getApplication();
+
+ /**
+ * Gets the virtual filename for this resource.
+ *
+ * @return the file name associated to this resource.
+ */
+ public String getFilename();
+
+ /**
+ * Gets the length of cache expiration time.
+ *
+ * <p>
+ * This gives the adapter the possibility cache streams sent to the client.
+ * The caching may be made in adapter or at the client if the client
+ * supports caching. Default is <code>DEFAULT_CACHETIME</code>.
+ * </p>
+ *
+ * @return Cache time in milliseconds
+ */
+ public long getCacheTime();
+
+ /**
+ * Gets the size of the download buffer used for this resource.
+ *
+ * <p>
+ * If the buffer size is 0, the buffer size is decided by the terminal
+ * adapter. The default value is 0.
+ * </p>
+ *
+ * @return int the size of the buffer in bytes.
+ */
+ public int getBufferSize();
+
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal;
+
+import com.itmill.toolkit.Application;
+import com.itmill.toolkit.service.FileTypeResolver;
+
+/**
+ * <code>ClassResource</code> is a named resource accessed with the class
+ * loader.
+ *
+ * This can be used to access resources such as icons, files, etc.
+ *
+ * @see java.lang.Class#getResource(java.lang.String)
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+public class ClassResource implements ApplicationResource {
+
+ /**
+ * Default buffer size for this stream resource.
+ */
+ private int bufferSize = 0;
+
+ /**
+ * Default cache time for this stream resource.
+ */
+ private long cacheTime = DEFAULT_CACHETIME;
+
+ /**
+ * Associated class used for indetifying the source of the resource.
+ */
+ private final Class associatedClass;
+
+ /**
+ * Name of the resource is relative to the associated class.
+ */
+ private final String resourceName;
+
+ /**
+ * Application used for serving the class.
+ */
+ private final Application application;
+
+ /**
+ * Creates a new application resource instance. The resource id is relative
+ * to the location of the application class.
+ *
+ * @param resourceName
+ * the Unique identifier of the resource within the
+ * application.
+ * @param application
+ * the application this resource will be added to.
+ */
+ public ClassResource(String resourceName, Application application) {
+ associatedClass = application.getClass();
+ this.resourceName = resourceName;
+ this.application = application;
+ if (resourceName == null) {
+ throw new NullPointerException();
+ }
+ application.addResource(this);
+ }
+
+ /**
+ * Creates a new application resource instance.
+ *
+ * @param associatedClass
+ * the class of the which the resource is associated.
+ * @param resourceName
+ * the Unique identifier of the resource within the
+ * application.
+ * @param application
+ * the application this resource will be added to.
+ */
+ public ClassResource(Class associatedClass, String resourceName,
+ Application application) {
+ this.associatedClass = associatedClass;
+ this.resourceName = resourceName;
+ this.application = application;
+ if (resourceName == null || associatedClass == null) {
+ throw new NullPointerException();
+ }
+ application.addResource(this);
+ }
+
+ /**
+ * Gets the MIME type of this resource.
+ *
+ * @see com.itmill.toolkit.terminal.Resource#getMIMEType()
+ */
+ public String getMIMEType() {
+ return FileTypeResolver.getMIMEType(resourceName);
+ }
+
+ /**
+ * Gets the application of this resource.
+ *
+ * @see com.itmill.toolkit.terminal.ApplicationResource#getApplication()
+ */
+ public Application getApplication() {
+ return application;
+ }
+
+ /**
+ * Gets the virtual filename for this resource.
+ *
+ * @return the file name associated to this resource.
+ * @see com.itmill.toolkit.terminal.ApplicationResource#getFilename()
+ */
+ public String getFilename() {
+ int index = 0;
+ int next = 0;
+ while ((next = resourceName.indexOf('/', index)) > 0
+ && next + 1 < resourceName.length()) {
+ index = next + 1;
+ }
+ return resourceName.substring(index);
+ }
+
+ /**
+ * Gets resource as stream.
+ *
+ * @see com.itmill.toolkit.terminal.ApplicationResource#getStream()
+ */
+ public DownloadStream getStream() {
+ final DownloadStream ds = new DownloadStream(associatedClass
+ .getResourceAsStream(resourceName), getMIMEType(),
+ getFilename());
+ ds.setBufferSize(getBufferSize());
+ ds.setCacheTime(cacheTime);
+ return ds;
+ }
+
+ /* documented in superclass */
+ public int getBufferSize() {
+ return bufferSize;
+ }
+
+ /**
+ * Sets the size of the download buffer used for this resource.
+ *
+ * @param bufferSize
+ * the size of the buffer in bytes.
+ */
+ public void setBufferSize(int bufferSize) {
+ this.bufferSize = bufferSize;
+ }
+
+ /* documented in superclass */
+ public long getCacheTime() {
+ return cacheTime;
+ }
+
+ /**
+ * Sets the length of cache expiration time.
+ *
+ * <p>
+ * This gives the adapter the possibility cache streams sent to the client.
+ * The caching may be made in adapter or at the client if the client
+ * supports caching. Zero or negavive value disbales the caching of this
+ * stream.
+ * </p>
+ *
+ * @param cacheTime
+ * the cache time in milliseconds.
+ *
+ */
+ public void setCacheTime(long cacheTime) {
+ this.cacheTime = cacheTime;
+ }
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Class for combining multiple error messages together.
+ *
+ * @author IT Mill Ltd
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+public class CompositeErrorMessage implements ErrorMessage {
+
+ /**
+ * Array of all the errors.
+ */
+ private final List errors;
+
+ /**
+ * Level of the error.
+ */
+ private int level;
+
+ /**
+ * Constructor for CompositeErrorMessage.
+ *
+ * @param errorMessages
+ * the Array of error messages that are listed togeter. Nulls
+ * are ignored, but at least one message is required.
+ */
+ public CompositeErrorMessage(ErrorMessage[] errorMessages) {
+ errors = new ArrayList(errorMessages.length);
+ level = Integer.MIN_VALUE;
+
+ for (int i = 0; i < errorMessages.length; i++) {
+ addErrorMessage(errorMessages[i]);
+ }
+
+ if (errors.size() == 0) {
+ throw new IllegalArgumentException(
+ "Composite error message must have at least one error");
+ }
+
+ }
+
+ /**
+ * Constructor for CompositeErrorMessage.
+ *
+ * @param errorMessages
+ * the Collection of error messages that are listed togeter.
+ * At least one message is required.
+ */
+ public CompositeErrorMessage(Collection errorMessages) {
+ errors = new ArrayList(errorMessages.size());
+ level = Integer.MIN_VALUE;
+
+ for (final Iterator i = errorMessages.iterator(); i.hasNext();) {
+ addErrorMessage((ErrorMessage) i.next());
+ }
+
+ if (errors.size() == 0) {
+ throw new IllegalArgumentException(
+ "Composite error message must have at least one error");
+ }
+ }
+
+ /**
+ * The error level is the largest error level in
+ *
+ * @see com.itmill.toolkit.terminal.ErrorMessage#getErrorLevel()
+ */
+ public final int getErrorLevel() {
+ return level;
+ }
+
+ /**
+ * Adds a error message into this composite message. Updates the level
+ * field.
+ *
+ * @param error
+ * the error message to be added. Duplicate errors are
+ * ignored.
+ */
+ private void addErrorMessage(ErrorMessage error) {
+ if (error != null && !errors.contains(error)) {
+ errors.add(error);
+ final int l = error.getErrorLevel();
+ if (l > level) {
+ level = l;
+ }
+ }
+ }
+
+ /**
+ * Gets Error Iterator.
+ *
+ * @return the error iterator.
+ */
+ public Iterator iterator() {
+ return errors.iterator();
+ }
+
+ /**
+ * @see com.itmill.toolkit.terminal.Paintable#paint(com.itmill.toolkit.terminal.PaintTarget)
+ */
+ public void paint(PaintTarget target) throws PaintException {
+
+ if (errors.size() == 1) {
+ ((ErrorMessage) errors.iterator().next()).paint(target);
+ } else {
+ target.startTag("error");
+
+ if (level > 0 && level <= ErrorMessage.INFORMATION) {
+ target.addAttribute("level", "info");
+ } else if (level <= ErrorMessage.WARNING) {
+ target.addAttribute("level", "warning");
+ } else if (level <= ErrorMessage.ERROR) {
+ target.addAttribute("level", "error");
+ } else if (level <= ErrorMessage.CRITICAL) {
+ target.addAttribute("level", "critical");
+ } else {
+ target.addAttribute("level", "system");
+ }
+
+ // Paint all the exceptions
+ for (final Iterator i = errors.iterator(); i.hasNext();) {
+ ((ErrorMessage) i.next()).paint(target);
+ }
+
+ target.endTag("error");
+ }
+ }
+
+ /* Documented in super interface */
+ public void addListener(RepaintRequestListener listener) {
+ }
+
+ /* Documented in super interface */
+ public void removeListener(RepaintRequestListener listener) {
+ }
+
+ /* Documented in super interface */
+ public void requestRepaint() {
+ }
+
+ /* Documented in super interface */
+ public void requestRepaintRequests() {
+ }
+
+ /**
+ * Returns a comma separated list of the error messages.
+ *
+ * @return String, comma separated list of error messages.
+ */
+ public String toString() {
+ String retval = "[";
+ int pos = 0;
+ for (final Iterator i = errors.iterator(); i.hasNext();) {
+ if (pos > 0) {
+ retval += ",";
+ }
+ pos++;
+ retval += i.next().toString();
+ }
+ retval += "]";
+
+ return retval;
+ }
+
+ public String getDebugId() {
+ return null;
+ }
+
+ public void setDebugId(String id) {
+ throw new UnsupportedOperationException(
+ "Setting testing id for this Paintable is not implemented");
+ }
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal;
+
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * Downloadable stream.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+public class DownloadStream {
+
+ /**
+ * Maximum cache time.
+ */
+ public static final long MAX_CACHETIME = Long.MAX_VALUE;
+
+ /**
+ * Default cache time.
+ */
+ public static final long DEFAULT_CACHETIME = 1000 * 60 * 60 * 24;
+
+ private InputStream stream;
+
+ private String contentType;
+
+ private String fileName;
+
+ private Map params;
+
+ private long cacheTime = DEFAULT_CACHETIME;
+
+ private int bufferSize = 0;
+
+ /**
+ * Creates a new instance of DownloadStream.
+ */
+ public DownloadStream(InputStream stream, String contentType,
+ String fileName) {
+ setStream(stream);
+ setContentType(contentType);
+ setFileName(fileName);
+ }
+
+ /**
+ * Gets downloadable stream.
+ *
+ * @return output stream.
+ */
+ public InputStream getStream() {
+ return stream;
+ }
+
+ /**
+ * Sets the stream.
+ *
+ * @param stream
+ * The stream to set
+ */
+ public void setStream(InputStream stream) {
+ this.stream = stream;
+ }
+
+ /**
+ * Gets stream content type.
+ *
+ * @return type of the stream content.
+ */
+ public String getContentType() {
+ return contentType;
+ }
+
+ /**
+ * Sets stream content type.
+ *
+ * @param contentType
+ * the contentType to set
+ */
+ public void setContentType(String contentType) {
+ this.contentType = contentType;
+ }
+
+ /**
+ * Returns the file name.
+ *
+ * @return the name of the file.
+ */
+ public String getFileName() {
+ return fileName;
+ }
+
+ /**
+ * Sets the file name.
+ *
+ * @param fileName
+ * the file name to set.
+ */
+ public void setFileName(String fileName) {
+ this.fileName = fileName;
+ }
+
+ /**
+ * Sets a paramater for download stream. Parameters are optional information
+ * about the downloadable stream and their meaning depends on the used
+ * adapter. For example in WebAdapter they are interpreted as HTTP response
+ * headers.
+ *
+ * If the parameters by this name exists, the old value is replaced.
+ *
+ * @param name
+ * the Name of the parameter to set.
+ * @param value
+ * the Value of the parameter to set.
+ */
+ public void setParameter(String name, String value) {
+ if (params == null) {
+ params = new HashMap();
+ }
+ params.put(name, value);
+ }
+
+ /**
+ * Gets a paramater for download stream. Parameters are optional information
+ * about the downloadable stream and their meaning depends on the used
+ * adapter. For example in WebAdapter they are interpreted as HTTP response
+ * headers.
+ *
+ * @param name
+ * the Name of the parameter to set.
+ * @return Value of the parameter or null if the parameter does not exist.
+ */
+ public String getParameter(String name) {
+ if (params != null) {
+ return (String) params.get(name);
+ }
+ return null;
+ }
+
+ /**
+ * Gets the names of the parameters.
+ *
+ * @return Iterator of names or null if no parameters are set.
+ */
+ public Iterator getParameterNames() {
+ if (params != null) {
+ return params.keySet().iterator();
+ }
+ return null;
+ }
+
+ /**
+ * Gets length of cache expiration time. This gives the adapter the
+ * possibility cache streams sent to the client. The caching may be made in
+ * adapter or at the client if the client supports caching. Default is
+ * <code>DEFAULT_CACHETIME</code>.
+ *
+ * @return Cache time in milliseconds
+ */
+ public long getCacheTime() {
+ return cacheTime;
+ }
+
+ /**
+ * Sets length of cache expiration time. This gives the adapter the
+ * possibility cache streams sent to the client. The caching may be made in
+ * adapter or at the client if the client supports caching. Zero or negavive
+ * value disbales the caching of this stream.
+ *
+ * @param cacheTime
+ * the cache time in milliseconds.
+ */
+ public void setCacheTime(long cacheTime) {
+ this.cacheTime = cacheTime;
+ }
+
+ /**
+ * Gets the size of the download buffer.
+ *
+ * @return int The size of the buffer in bytes.
+ */
+ public int getBufferSize() {
+ return bufferSize;
+ }
+
+ /**
+ * Sets the size of the download buffer.
+ *
+ * @param bufferSize
+ * the size of the buffer in bytes.
+ */
+ public void setBufferSize(int bufferSize) {
+ this.bufferSize = bufferSize;
+ }
+
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal;
+
+/**
+ * Interface for rendering error messages to terminal. All the visible errors
+ * shown to user must implement this interface.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+public interface ErrorMessage extends Paintable {
+
+ /**
+ * Error code for system errors and bugs.
+ */
+ public static final int SYSTEMERROR = 5000;
+
+ /**
+ * Error code for critical error messages.
+ */
+ public static final int CRITICAL = 4000;
+
+ /**
+ * Error code for regular error messages.
+ */
+ public static final int ERROR = 3000;
+
+ /**
+ * Error code for warning messages.
+ */
+ public static final int WARNING = 2000;
+
+ /**
+ * Error code for informational messages.
+ */
+ public static final int INFORMATION = 1000;
+
+ /**
+ * Gets the errors level.
+ *
+ * @return the level of error as an integer.
+ */
+ public int getErrorLevel();
+
+ /**
+ * Error messages are inmodifiable and thus listeners are not needed. This
+ * method should be implemented as empty.
+ *
+ * @param listener
+ * the listener to be added.
+ * @see com.itmill.toolkit.terminal.Paintable#addListener(Paintable.RepaintRequestListener)
+ */
+ public void addListener(RepaintRequestListener listener);
+
+ /**
+ * Error messages are inmodifiable and thus listeners are not needed. This
+ * method should be implemented as empty.
+ *
+ * @param listener
+ * the listener to be removed.
+ * @see com.itmill.toolkit.terminal.Paintable#removeListener(Paintable.RepaintRequestListener)
+ */
+ public void removeListener(RepaintRequestListener listener);
+
+ /**
+ * Error messages are inmodifiable and thus listeners are not needed. This
+ * method should be implemented as empty.
+ *
+ * @see com.itmill.toolkit.terminal.Paintable#requestRepaint()
+ */
+ public void requestRepaint();
+
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal;
+
+import java.net.URL;
+
+import com.itmill.toolkit.service.FileTypeResolver;
+
+/**
+ * <code>ExternalResource</code> implements source for resources fetched from
+ * location specified by URL:s. The resources are fetched directly by the client
+ * terminal and are not fetched trough the terminal adapter.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+public class ExternalResource implements Resource {
+
+ /**
+ * Url of the download.
+ */
+ private String sourceURL = null;
+
+ /**
+ * Creates a new download component for downloading directly from given URL.
+ *
+ * @param sourceURL
+ * the source URL.
+ */
+ public ExternalResource(URL sourceURL) {
+ if (sourceURL == null) {
+ throw new RuntimeException("Source must be non-null");
+ }
+
+ this.sourceURL = sourceURL.toString();
+ }
+
+ /**
+ * Creates a new download component for downloading directly from given URL.
+ *
+ * @param sourceURL
+ * the source URL.
+ */
+ public ExternalResource(String sourceURL) {
+ if (sourceURL == null) {
+ throw new RuntimeException("Source must be non-null");
+ }
+
+ this.sourceURL = sourceURL.toString();
+ }
+
+ /**
+ * Gets the URL of the external resource.
+ *
+ * @return the URL of the external resource.
+ */
+ public String getURL() {
+ return sourceURL;
+ }
+
+ /**
+ * Gets the MIME type of the resource.
+ *
+ * @see com.itmill.toolkit.terminal.Resource#getMIMEType()
+ */
+ public String getMIMEType() {
+ return FileTypeResolver.getMIMEType(getURL().toString());
+ }
+
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+
+import com.itmill.toolkit.Application;
+import com.itmill.toolkit.service.FileTypeResolver;
+
+/**
+ * <code>FileResources</code> are files or directories on local filesystem.
+ * The files and directories are served through URI:s to the client terminal and
+ * thus must be registered to an URI context before they can be used. The
+ * resource is automatically registered to the application when it is created.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+public class FileResource implements ApplicationResource {
+
+ /**
+ * Default buffer size for this stream resource.
+ */
+ private int bufferSize = 0;
+
+ /**
+ * File where the downloaded content is fetched from.
+ */
+ private File sourceFile;
+
+ /**
+ * Application.
+ */
+ private final Application application;
+
+ /**
+ * Default cache time for this stream resource.
+ */
+ private long cacheTime = DownloadStream.DEFAULT_CACHETIME;
+
+ /**
+ * Creates a new file resource for providing given file for client
+ * terminals.
+ */
+ public FileResource(File sourceFile, Application application) {
+ this.application = application;
+ setSourceFile(sourceFile);
+ application.addResource(this);
+ }
+
+ /**
+ * Gets the resource as stream.
+ *
+ * @see com.itmill.toolkit.terminal.ApplicationResource#getStream()
+ */
+ public DownloadStream getStream() {
+ try {
+ final DownloadStream ds = new DownloadStream(new FileInputStream(
+ sourceFile), getMIMEType(), getFilename());
+ ds.setCacheTime(cacheTime);
+ return ds;
+ } catch (final FileNotFoundException e) {
+ // No logging for non-existing files at this level.
+ return null;
+ }
+ }
+
+ /**
+ * Gets the source file.
+ *
+ * @return the source File.
+ */
+ public File getSourceFile() {
+ return sourceFile;
+ }
+
+ /**
+ * Sets the source file.
+ *
+ * @param sourceFile
+ * the source file to set.
+ */
+ public void setSourceFile(File sourceFile) {
+ this.sourceFile = sourceFile;
+ }
+
+ /**
+ * @see com.itmill.toolkit.terminal.ApplicationResource#getApplication()
+ */
+ public Application getApplication() {
+ return application;
+ }
+
+ /**
+ * @see com.itmill.toolkit.terminal.ApplicationResource#getFilename()
+ */
+ public String getFilename() {
+ return sourceFile.getName();
+ }
+
+ /**
+ * @see com.itmill.toolkit.terminal.Resource#getMIMEType()
+ */
+ public String getMIMEType() {
+ return FileTypeResolver.getMIMEType(sourceFile);
+ }
+
+ /**
+ * Gets the length of cache expiration time. This gives the adapter the
+ * possibility cache streams sent to the client. The caching may be made in
+ * adapter or at the client if the client supports caching. Default is
+ * <code>DownloadStream.DEFAULT_CACHETIME</code>.
+ *
+ * @return Cache time in milliseconds.
+ */
+ public long getCacheTime() {
+ return cacheTime;
+ }
+
+ /**
+ * Sets the length of cache expiration time. This gives the adapter the
+ * possibility cache streams sent to the client. The caching may be made in
+ * adapter or at the client if the client supports caching. Zero or negavive
+ * value disbales the caching of this stream.
+ *
+ * @param cacheTime
+ * the cache time in milliseconds.
+ */
+ public void setCacheTime(long cacheTime) {
+ this.cacheTime = cacheTime;
+ }
+
+ /* documented in superclass */
+ public int getBufferSize() {
+ return bufferSize;
+ }
+
+ /**
+ * Sets the size of the download buffer used for this resource.
+ *
+ * @param bufferSize
+ * the size of the buffer in bytes.
+ */
+ public void setBufferSize(int bufferSize) {
+ this.bufferSize = bufferSize;
+ }
+
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal;
+
+import java.util.Hashtable;
+
+/**
+ * <code>KeyMapper</code> is the simple two-way map for generating textual
+ * keys for objects and retrieving the objects later with the key.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+public class KeyMapper {
+
+ private int lastKey = 0;
+
+ private final Hashtable objectKeyMap = new Hashtable();
+
+ private final Hashtable keyObjectMap = new Hashtable();
+
+ /**
+ * Gets key for an object.
+ *
+ * @param o
+ * the object.
+ */
+ public String key(Object o) {
+
+ if (o == null) {
+ return "null";
+ }
+
+ // If the object is already mapped, use existing key
+ String key = (String) objectKeyMap.get(o);
+ if (key != null) {
+ return key;
+ }
+
+ // If the object is not yet mapped, map it
+ key = String.valueOf(++lastKey);
+ objectKeyMap.put(o, key);
+ keyObjectMap.put(key, o);
+
+ return key;
+ }
+
+ /**
+ * Retrieves object with the key.
+ *
+ * @param key
+ * the name with the desired value.
+ * @return the object with the key.
+ */
+ public Object get(String key) {
+
+ return keyObjectMap.get(key);
+ }
+
+ /**
+ * Removes object from the mapper.
+ *
+ * @param removeobj
+ * the object to be removed.
+ */
+ public void remove(Object removeobj) {
+ final String key = (String) objectKeyMap.get(removeobj);
+
+ if (key != null) {
+ objectKeyMap.remove(key);
+ keyObjectMap.remove(removeobj);
+ }
+ }
+
+ /**
+ * Removes all objects from the mapper.
+ */
+ public void removeAll() {
+ objectKeyMap.clear();
+ keyObjectMap.clear();
+ }
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal;
+
+import java.io.IOException;
+
+/**
+ * <code>PaintExcepection</code> is thrown if painting of a component fails.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+public class PaintException extends IOException {
+
+ /**
+ * Serial generated by eclipse.
+ */
+ private static final long serialVersionUID = 3762535607221891897L;
+
+ /**
+ * Constructs an instance of <code>PaintExeception</code> with the
+ * specified detail message.
+ *
+ * @param msg
+ * the detail message.
+ */
+ public PaintException(String msg) {
+ super(msg);
+ }
+
+ /**
+ * Constructs an instance of <code>PaintExeception</code> from
+ * IOException.
+ *
+ * @param exception
+ * the original exception.
+ */
+ public PaintException(IOException exception) {
+ super(exception.getMessage());
+ }
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal;
+
+/**
+ * This interface defines the methods for painting XML to the UIDL stream.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+public interface PaintTarget {
+
+ /**
+ * Prints single XMLsection.
+ *
+ * Prints full XML section. The section data is escaped from XML tags and
+ * surrounded by XML start and end-tags.
+ *
+ * @param sectionTagName
+ * the name of the tag.
+ * @param sectionData
+ * the scetion data.
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ public void addSection(String sectionTagName, String sectionData)
+ throws PaintException;
+
+ /**
+ * Prints element start tag of a paintable section. Starts a paintable
+ * section using the given tag. The PaintTarget may implement a caching
+ * scheme, that checks the paintable has actually changed or can a cached
+ * version be used instead. This method should call the startTag method.
+ * <p>
+ * If the Paintable is found in cache and this function returns true it may
+ * omit the content and close the tag, in which case cached content should
+ * be used.
+ * </p>
+ *
+ * @param paintable
+ * the paintable to start.
+ * @param tag
+ * the name of the start tag.
+ * @return <code>true</code> if paintable found in cache,
+ * <code>false</code> otherwise.
+ * @throws PaintException
+ * if the paint operation failed.
+ * @see #startTag(String)
+ * @since 3.1
+ */
+ public boolean startTag(Paintable paintable, String tag)
+ throws PaintException;
+
+ /**
+ * Paints a component reference as an attribute to current tag. This method
+ * is meant to enable component interactions on client side. With reference
+ * the client side component can communicate directly to other component.
+ *
+ * Note! This is still an experimental feature and API is likely to change
+ * in future.
+ *
+ * @param paintable
+ * the Paintable to reference
+ * @param referenceName
+ * @throws PaintException
+ *
+ * @since 5.2
+ */
+ public void paintReference(Paintable paintable, String referenceName)
+ throws PaintException;
+
+ /**
+ * Prints element start tag.
+ *
+ * <pre>
+ * Todo:
+ * Checking of input values
+ * </pre>
+ *
+ * @param tagName
+ * the name of the start tag.
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ public void startTag(String tagName) throws PaintException;
+
+ /**
+ * Prints element end tag.
+ *
+ * If the parent tag is closed before every child tag is closed an
+ * PaintException is raised.
+ *
+ * @param tagName
+ * the name of the end tag.
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ public void endTag(String tagName) throws PaintException;
+
+ /**
+ * Adds a boolean attribute to component. Atributes must be added before any
+ * content is written.
+ *
+ * @param name
+ * the Attribute name.
+ * @param value
+ * the Attribute value.
+ *
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ public void addAttribute(String name, boolean value) throws PaintException;
+
+ /**
+ * Adds a integer attribute to component. Atributes must be added before any
+ * content is written.
+ *
+ * @param name
+ * the Attribute name.
+ * @param value
+ * the Attribute value.
+ *
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ public void addAttribute(String name, int value) throws PaintException;
+
+ /**
+ * Adds a resource attribute to component. Atributes must be added before
+ * any content is written.
+ *
+ * @param name
+ * the Attribute name
+ * @param value
+ * the Attribute value
+ *
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ public void addAttribute(String name, Resource value) throws PaintException;
+
+ /**
+ * Adds a long attribute to component. Atributes must be added before any
+ * content is written.
+ *
+ * @param name
+ * the Attribute name.
+ * @param value
+ * the Attribute value.
+ *
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ public void addAttribute(String name, long value) throws PaintException;
+
+ /**
+ * Adds a float attribute to component. Atributes must be added before any
+ * content is written.
+ *
+ * @param name
+ * the Attribute name.
+ * @param value
+ * the Attribute value.
+ *
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ public void addAttribute(String name, float value) throws PaintException;
+
+ /**
+ * Adds a double attribute to component. Atributes must be added before any
+ * content is written.
+ *
+ * @param name
+ * the Attribute name.
+ * @param value
+ * the Attribute value.
+ *
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ public void addAttribute(String name, double value) throws PaintException;
+
+ /**
+ * Adds a string attribute to component. Atributes must be added before any
+ * content is written.
+ *
+ * @param name
+ * the Boolean attribute name.
+ * @param value
+ * the Boolean attribute value.
+ *
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ public void addAttribute(String name, String value) throws PaintException;
+
+ /**
+ * Adds a string type variable.
+ *
+ * @param owner
+ * the Listener for variable changes.
+ * @param name
+ * the Variable name.
+ * @param value
+ * the Variable initial value.
+ *
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ public void addVariable(VariableOwner owner, String name, String value)
+ throws PaintException;
+
+ /**
+ * Adds a int type variable.
+ *
+ * @param owner
+ * the Listener for variable changes.
+ * @param name
+ * the Variable name.
+ * @param value
+ * the Variable initial value.
+ *
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ public void addVariable(VariableOwner owner, String name, int value)
+ throws PaintException;
+
+ /**
+ * Adds a long type variable.
+ *
+ * @param owner
+ * the Listener for variable changes.
+ * @param name
+ * the Variable name.
+ * @param value
+ * the Variable initial value.
+ *
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ public void addVariable(VariableOwner owner, String name, long value)
+ throws PaintException;
+
+ /**
+ * Adds a float type variable.
+ *
+ * @param owner
+ * the Listener for variable changes.
+ * @param name
+ * the Variable name.
+ * @param value
+ * the Variable initial value.
+ *
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ public void addVariable(VariableOwner owner, String name, float value)
+ throws PaintException;
+
+ /**
+ * Adds a double type variable.
+ *
+ * @param owner
+ * the Listener for variable changes.
+ * @param name
+ * the Variable name.
+ * @param value
+ * the Variable initial value.
+ *
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ public void addVariable(VariableOwner owner, String name, double value)
+ throws PaintException;
+
+ /**
+ * Adds a boolean type variable.
+ *
+ * @param owner
+ * the Listener for variable changes.
+ * @param name
+ * the Variable name.
+ * @param value
+ * the Variable initial value.
+ *
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ public void addVariable(VariableOwner owner, String name, boolean value)
+ throws PaintException;
+
+ /**
+ * Adds a string array type variable.
+ *
+ * @param owner
+ * the Listener for variable changes.
+ * @param name
+ * the Variable name.
+ * @param value
+ * the Variable initial value.
+ *
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ public void addVariable(VariableOwner owner, String name, String[] value)
+ throws PaintException;
+
+ /**
+ * Adds a upload stream type variable.
+ *
+ * @param owner
+ * the Listener for variable changes.
+ * @param name
+ * the Variable name.
+ *
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ public void addUploadStreamVariable(VariableOwner owner, String name)
+ throws PaintException;
+
+ /**
+ * Prints single XML section.
+ * <p>
+ * Prints full XML section. The section data must be XML and it is
+ * surrounded by XML start and end-tags.
+ * </p>
+ *
+ * @param sectionTagName
+ * the tag name.
+ * @param sectionData
+ * the section data to be printed.
+ * @param namespace
+ * the namespace.
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ public void addXMLSection(String sectionTagName, String sectionData,
+ String namespace) throws PaintException;
+
+ /**
+ * Adds UIDL directly. The UIDL must be valid in accordance with the
+ * UIDL.dtd
+ *
+ * @param uidl
+ * the UIDL to be added.
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ public void addUIDL(java.lang.String uidl) throws PaintException;
+
+ /**
+ * Adds text node. All the contents of the text are XML-escaped.
+ *
+ * @param text
+ * the Text to add
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ void addText(String text) throws PaintException;
+
+ /**
+ * Adds CDATA node to target UIDL-tree.
+ *
+ * @param text
+ * the Character data to add
+ * @throws PaintException
+ * if the paint operation failed.
+ * @since 3.1
+ */
+ void addCharacterData(String text) throws PaintException;
+
+ public void addAttribute(String string, Object[] keys);
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal;
+
+import java.util.EventObject;
+
+/**
+ * Interface implemented by all classes that can be painted. Classes
+ * implementing this interface know how to output themselves to a UIDL stream
+ * and that way describing to the terminal how it should be displayed in the UI.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+public interface Paintable extends java.util.EventListener {
+
+ /**
+ * <p>
+ * Paints the Paintable into a UIDL stream. This method creates the UIDL
+ * sequence describing it and outputs it to the given UIDL stream.
+ * </p>
+ *
+ * <p>
+ * It is called when the contents of the component should be painted in
+ * response to the component first being shown or having been altered so
+ * that its visual representation is changed.
+ * </p>
+ *
+ * @param target
+ * the target UIDL stream where the component should paint
+ * itself to.
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ public void paint(PaintTarget target) throws PaintException;
+
+ /**
+ * Requests that the paintable should be repainted as soon as possible.
+ */
+ public void requestRepaint();
+
+ /**
+ * Adds an unique id for component that get's transferred to terminal for
+ * testing purposes. Keeping identifiers unique throughout the Application
+ * instance is on programmers responsibility.
+ *
+ * @param id
+ * A short (< 20 chars) alphanumeric id
+ */
+ public void setDebugId(String id);
+
+ /**
+ * Get's currently set debug identifier
+ *
+ * @return current debug id, null if not set
+ */
+ public String getDebugId();
+
+ /**
+ * Repaint request event is thrown when the paintable needs to be repainted.
+ * This is typically done when the <code>paint</code> method would return
+ * dissimilar UIDL from the previous call of the method.
+ */
+ public class RepaintRequestEvent extends EventObject {
+
+ /**
+ * Serial generated by eclipse.
+ */
+ private static final long serialVersionUID = 3256725095530442805L;
+
+ /**
+ * Constructs a new event.
+ *
+ * @param source
+ * the paintable needing repaint.
+ */
+ public RepaintRequestEvent(Paintable source) {
+ super(source);
+ }
+
+ /**
+ * Gets the paintable needing repainting.
+ *
+ * @return Paintable for which the <code>paint</code> method will
+ * return dissimilar UIDL from the previous call of the method.
+ */
+ public Paintable getPaintable() {
+ return (Paintable) getSource();
+ }
+ }
+
+ /**
+ * Listens repaint requests. The <code>repaintRequested</code> method is
+ * called when the paintable needs to be repainted. This is typically done
+ * when the <code>paint</code> method would return dissimilar UIDL from
+ * the previous call of the method.
+ */
+ public interface RepaintRequestListener {
+
+ /**
+ * Receives repaint request events.
+ *
+ * @param event
+ * the repaint request event specifying the paintable
+ * source.
+ */
+ public void repaintRequested(RepaintRequestEvent event);
+ }
+
+ /**
+ * Adds repaint request listener. In order to assure that no repaint
+ * requests are missed, the new repaint listener should paint the paintable
+ * right after adding itself as listener.
+ *
+ * @param listener
+ * the listener to be added.
+ */
+ public void addListener(RepaintRequestListener listener);
+
+ /**
+ * Removes repaint request listener.
+ *
+ * @param listener
+ * the listener to be removed.
+ */
+ public void removeListener(RepaintRequestListener listener);
+
+ /**
+ * Request sending of repaint events on any further visible changes.
+ * Normally the paintable only send up to one repaint request for listeners
+ * after paint as the paintable as the paintable assumes that the listeners
+ * already know about the repaint need. This method resets the assumtion.
+ * Paint implicitly does the assumtion reset functionality implemented by
+ * this method.
+ * <p>
+ * This method is normally used only by the terminals to note paintables
+ * about implicit repaints (painting the component without actually invoking
+ * paint method).
+ * </p>
+ */
+ public void requestRepaintRequests();
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal;
+
+import java.util.Map;
+
+/**
+ * Interface implemented by all the classes capable of handling external
+ * parameters.
+ *
+ * <p>
+ * Some terminals can provide external parameters for application. For example
+ * GET and POST parameters are passed to application as external parameters on
+ * Web Adapter. The parameters can be received at any time during the
+ * application lifecycle. All the parameter handlers implementing this interface
+ * and registered to {@link com.itmill.toolkit.ui.Window} receive all the
+ * parameters got from the terminal in the given window.
+ * </p>
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+public interface ParameterHandler {
+
+ /**
+ * <p>
+ * Handles the given parameters. The parameters are given as inmodifieable
+ * name to value map. All parameters names are of type:
+ * {@link java.lang.String}. All the parameter values are arrays of
+ * strings.
+ * </p>
+ *
+ * @param parameters
+ * the Inmodifiable name to value[] mapping.
+ *
+ */
+ public void handleParameters(Map parameters);
+
+ /**
+ * ParameterHandler error event.
+ */
+ public interface ErrorEvent extends Terminal.ErrorEvent {
+
+ /**
+ * Gets the source ParameterHandler.
+ *
+ * @return the source Parameter Handler.
+ */
+ public ParameterHandler getParameterHandler();
+
+ }
+
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal;
+
+/**
+ * <code>Resource</code> provided to the client terminal. Support for actually
+ * displaying the resource type is left to the terminal.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+public interface Resource {
+
+ /**
+ * Gets the MIME type of the resource.
+ *
+ * @return the MIME type of the resource.
+ */
+ public String getMIMEType();
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal;
+
+/**
+ * <p>
+ * This interface is implemented by all visual objects that can be scrolled. The
+ * unit of scrolling is pixel.
+ * </p>
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+public interface Scrollable {
+
+ /**
+ * Gets scroll left offset.
+ *
+ * <p>
+ * Scrolling offset is the number of pixels this scrollable has been
+ * scrolled right.
+ * </p>
+ *
+ * @return Horizontal scrolling position in pixels.
+ */
+ public int getScrollLeft();
+
+ /**
+ * Sets scroll left offset.
+ *
+ * <p>
+ * Scrolling offset is the number of pixels this scrollable has been
+ * scrolled right.
+ * </p>
+ *
+ * @param pixelsScrolled
+ * the xOffset.
+ */
+ public void setScrollLeft(int pixelsScrolled);
+
+ /**
+ * Gets scroll top offset.
+ *
+ * <p>
+ * Scrolling offset is the number of pixels this scrollable has been
+ * scrolled down.
+ * </p>
+ *
+ * @return Vertical scrolling position in pixels.
+ */
+ public int getScrollTop();
+
+ /**
+ * Sets scroll top offset.
+ *
+ * <p>
+ * Scrolling offset is the number of pixels this scrollable has been
+ * scrolled down.
+ * </p>
+ *
+ * @param pixelsScrolled
+ * the yOffset.
+ */
+ public void setScrollTop(int pixelsScrolled);
+
+ /**
+ * Is the scrolling enabled.
+ *
+ * <p>
+ * Enabling scrolling allows the user to scroll the scrollable view
+ * interactively
+ * </p>
+ *
+ * @return <code>true</code> if the scrolling is allowed, otherwise
+ * <code>false</code>.
+ */
+ public boolean isScrollable();
+
+ /**
+ * Enables or disables scrolling..
+ *
+ * <p>
+ * Enabling scrolling allows the user to scroll the scrollable view
+ * interactively
+ * </p>
+ *
+ * @param isScrollingEnabled
+ * true if the scrolling is allowed.
+ */
+ public void setScrollable(boolean isScrollingEnabled);
+
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal;
+
+/**
+ * Interface to be implemented by components wishing to display some object that
+ * may be dynamically resized during runtime.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+public interface Sizeable {
+
+ /**
+ * Unit code representing pixels.
+ */
+ public static final int UNITS_PIXELS = 0;
+
+ /**
+ * Unit code representing points (1/72nd of an inch).
+ */
+ public static final int UNITS_POINTS = 1;
+
+ /**
+ * Unit code representing picas (12 points).
+ */
+ public static final int UNITS_PICAS = 2;
+
+ /**
+ * Unit code representing the font-size of the relevant font.
+ */
+ public static final int UNITS_EM = 3;
+
+ /**
+ * Unit code representing the x-height of the relevant font.
+ */
+ public static final int UNITS_EX = 4;
+
+ /**
+ * Unit code representing millimeters.
+ */
+ public static final int UNITS_MM = 5;
+
+ /**
+ * Unit code representing centimeters.
+ */
+ public static final int UNITS_CM = 6;
+
+ /**
+ * Unit code representing inches.
+ */
+ public static final int UNITS_INCH = 7;
+
+ /**
+ * Unit code representing in percentage of the containing element defined by
+ * terminal.
+ */
+ public static final int UNITS_PERCENTAGE = 8;
+
+ /**
+ * Unit code representing in rows of text. This unit is only applicaple to
+ * some components can it's meaning is specified by component
+ * implementation.
+ */
+ public static final int UNITS_ROWS = 9;
+
+ public static final int SIZE_UNDEFINED = -1;
+
+ /**
+ * Textual representations of units symbols. Supported units and their
+ * symbols are:
+ * <ul>
+ * <li><code>UNITS_PIXELS</code>: "px"</li>
+ * <li><code>UNITS_POINTS</code>: "pt"</li>
+ * <li><code>UNITS_PICAS</code>: "pc"</li>
+ * <li><code>UNITS_EM</code>: "em"</li>
+ * <li><code>UNITS_EX</code>: "ex"</li>
+ * <li><code>UNITS_MM</code>: "mm"</li>
+ * <li><code>UNITS_CM</code>. "cm"</li>
+ * <li><code>UNITS_INCH</code>: "in"</li>
+ * <li><code>UNITS_PERCENTAGE</code>: "%"</li>
+ * <li><code>UNITS_ROWS</code>: "rows"</li>
+ * </ul>
+ * These can be used like <code>Sizeable.UNIT_SYMBOLS[UNITS_PIXELS]</code>.
+ */
+ public static final String[] UNIT_SYMBOLS = { "px", "pt", "pc", "em", "ex",
+ "mm", "cm", "in", "%", "rows" };
+
+ /**
+ * Gets the width of the object. Negative number implies unspecified size
+ * (terminal is free to set the size).
+ *
+ * @return width of the object in units specified by widthUnits property.
+ */
+ public int getWidth();
+
+ /**
+ * Sets the width of the object. Negative number implies unspecified size
+ * (terminal is free to set the size).
+ *
+ * @param width
+ * the width of the object in units specified by widthUnits
+ * property.
+ */
+ public void setWidth(int width);
+
+ /**
+ * Gets the height of the object. Negative number implies unspecified size
+ * (terminal is free to set the size).
+ *
+ * @return height of the object in units specified by heightUnits property.
+ */
+ public int getHeight();
+
+ /**
+ * Sets the height of the object. Negative number implies unspecified size
+ * (terminal is free to set the size).
+ *
+ * @param height
+ * the height of the object in units specified by heightUnits
+ * property.
+ */
+ public void setHeight(int height);
+
+ /**
+ * Gets the width property units.
+ *
+ * @return units used in width property.
+ */
+ public int getWidthUnits();
+
+ /**
+ * Sets the width property units.
+ *
+ * @param units
+ * the units used in width property.
+ */
+ public void setWidthUnits(int units);
+
+ /**
+ * Gets the height property units.
+ *
+ * @return units used in height property.
+ */
+ public int getHeightUnits();
+
+ /**
+ * Sets the height property units.
+ *
+ * @param units
+ * the units used in height property.
+ */
+ public void setHeightUnits(int units);
+
+ /**
+ * Sets the height of the component using String presentation.
+ *
+ * String presentation is similar to what is used in Cascading Style Sheets.
+ * Size can be length or percentage of available size.
+ *
+ * See <a
+ * href="http://www.w3.org/TR/REC-CSS2/syndata.html#value-def-length">CSS
+ * spesification</a> for more details.
+ *
+ * @param height
+ * in CSS style string representation
+ */
+ public void setHeight(String height);
+
+ /**
+ * Sets the width of the component using String presentation.
+ *
+ * String presentation is similar to what is used in Cascading Style Sheets.
+ * Size can be length or percentage of available size.
+ *
+ * See <a
+ * href="http://www.w3.org/TR/REC-CSS2/syndata.html#value-def-length">CSS
+ * spesification</a> for more details.
+ *
+ * @param width
+ * in CSS style string representation
+ */
+ public void setWidth(String width);
+
+ /**
+ * Sets the size to 100% x 100%.
+ */
+ public void setSizeFull();
+
+ /**
+ * Clears any size settings.
+ */
+ public void setSizeUndefined();
+
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal;
+
+import java.io.InputStream;
+
+import com.itmill.toolkit.Application;
+import com.itmill.toolkit.service.FileTypeResolver;
+
+/**
+ * <code>StreamResource</code> is a resource provided to the client directly
+ * by the application. The strean resource is fetched from URI that is most
+ * often in the context of the application or window. The resource is
+ * automatically registered to window in creation.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+public class StreamResource implements ApplicationResource {
+
+ /**
+ * Source stream the downloaded content is fetched from.
+ */
+ private StreamSource streamSource = null;
+
+ /**
+ * Explicit mime-type.
+ */
+ private String MIMEType = null;
+
+ /**
+ * Filename.
+ */
+ private String filename;
+
+ /**
+ * Application.
+ */
+ private final Application application;
+
+ /**
+ * Default buffer size for this stream resource.
+ */
+ private int bufferSize = 0;
+
+ /**
+ * Default cache time for this stream resource.
+ */
+ private long cacheTime = DEFAULT_CACHETIME;
+
+ /**
+ * Creates a new stream resource for downloading from stream.
+ *
+ * @param streamSource
+ * the source Stream.
+ * @param filename
+ * the name of the file.
+ * @param application
+ * the Application object.
+ */
+ public StreamResource(StreamSource streamSource, String filename,
+ Application application) {
+
+ this.application = application;
+ setFilename(filename);
+ setStreamSource(streamSource);
+
+ // Register to application
+ application.addResource(this);
+
+ }
+
+ /**
+ * @see com.itmill.toolkit.terminal.Resource#getMIMEType()
+ */
+ public String getMIMEType() {
+ if (MIMEType != null) {
+ return MIMEType;
+ }
+ return FileTypeResolver.getMIMEType(filename);
+ }
+
+ /**
+ * Sets the mime type of the resource.
+ *
+ * @param MIMEType
+ * the MIME type to be set.
+ */
+ public void setMIMEType(String MIMEType) {
+ this.MIMEType = MIMEType;
+ }
+
+ /**
+ * Returns the source for this <code>StreamResource</code>. StreamSource
+ * is queried when the resource is about to be streamed to the client.
+ *
+ * @return Source of the StreamResource.
+ */
+ public StreamSource getStreamSource() {
+ return streamSource;
+ }
+
+ /**
+ * Sets the source for this <code>StreamResource</code>.
+ * <code>StreamSource</code> is queried when the resource is about to be
+ * streamed to the client.
+ *
+ * @param streamSource
+ * the source to set.
+ */
+ public void setStreamSource(StreamSource streamSource) {
+ this.streamSource = streamSource;
+ }
+
+ /**
+ * Gets the filename.
+ *
+ * @return the filename.
+ */
+ public String getFilename() {
+ return filename;
+ }
+
+ /**
+ * Sets the filename.
+ *
+ * @param filename
+ * the filename to set.
+ */
+ public void setFilename(String filename) {
+ this.filename = filename;
+ }
+
+ /**
+ * @see com.itmill.toolkit.terminal.ApplicationResource#getApplication()
+ */
+ public Application getApplication() {
+ return application;
+ }
+
+ /**
+ * @see com.itmill.toolkit.terminal.ApplicationResource#getStream()
+ */
+ public DownloadStream getStream() {
+ final StreamSource ss = getStreamSource();
+ if (ss == null) {
+ return null;
+ }
+ final DownloadStream ds = new DownloadStream(ss.getStream(),
+ getMIMEType(), getFilename());
+ ds.setBufferSize(getBufferSize());
+ ds.setCacheTime(cacheTime);
+ return ds;
+ }
+
+ /**
+ * Interface implemented by the source of a StreamResource.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ public interface StreamSource {
+
+ /**
+ * Returns new input stream that is used for reading the resource.
+ */
+ public InputStream getStream();
+ }
+
+ /* documented in superclass */
+ public int getBufferSize() {
+ return bufferSize;
+ }
+
+ /**
+ * Sets the size of the download buffer used for this resource.
+ *
+ * @param bufferSize
+ * the size of the buffer in bytes.
+ */
+ public void setBufferSize(int bufferSize) {
+ this.bufferSize = bufferSize;
+ }
+
+ /* documented in superclass */
+ public long getCacheTime() {
+ return cacheTime;
+ }
+
+ /**
+ * Sets the length of cache expiration time.
+ *
+ * <p>
+ * This gives the adapter the possibility cache streams sent to the client.
+ * The caching may be made in adapter or at the client if the client
+ * supports caching. Zero or negavive value disbales the caching of this
+ * stream.
+ * </p>
+ *
+ * @param cacheTime
+ * the cache time in milliseconds.
+ *
+ */
+ public void setCacheTime(long cacheTime) {
+ this.cacheTime = cacheTime;
+ }
+
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/**
+ * <code>SystemError</code> is a runtime exception caused by error in system.
+ * The system error can be shown to the user as it implements
+ * <code>ErrorMessage</code> interface, but contains technical information
+ * such as stack trace and exception.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+public class SystemError extends RuntimeException implements ErrorMessage {
+
+ /**
+ * Serial generated by eclipse.
+ */
+ private static final long serialVersionUID = 3256445789512675891L;
+
+ /**
+ * The cause of the system error. The cause is stored separately as JDK 1.3
+ * does not support causes natively.
+ */
+ private Throwable cause = null;
+
+ /**
+ * Constructor for SystemError with error message specified.
+ *
+ * @param message
+ * the Textual error description.
+ */
+ public SystemError(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructor for SystemError with causing exception and error message.
+ *
+ * @param message
+ * the Textual error description.
+ * @param cause
+ * the throwable causing the system error.
+ */
+ public SystemError(String message, Throwable cause) {
+ super(message);
+ this.cause = cause;
+ }
+
+ /**
+ * Constructor for SystemError with cause.
+ *
+ * @param cause
+ * the throwable causing the system error.
+ */
+ public SystemError(Throwable cause) {
+ this.cause = cause;
+ }
+
+ /**
+ * @see com.itmill.toolkit.terminal.ErrorMessage#getErrorLevel()
+ */
+ public final int getErrorLevel() {
+ return ErrorMessage.SYSTEMERROR;
+ }
+
+ /**
+ * @see com.itmill.toolkit.terminal.Paintable#paint(com.itmill.toolkit.terminal.PaintTarget)
+ */
+ public void paint(PaintTarget target) throws PaintException {
+
+ target.startTag("error");
+ target.addAttribute("level", "system");
+
+ // Paint the error message
+ final String message = getLocalizedMessage();
+ if (message != null) {
+ target.addSection("h2", message);
+ }
+
+ // Paint the exception
+ if (cause != null) {
+ target.addSection("h3", "Exception");
+ final StringWriter buffer = new StringWriter();
+ cause.printStackTrace(new PrintWriter(buffer));
+ target.addSection("pre", buffer.toString());
+ }
+
+ target.endTag("error");
+
+ }
+
+ /**
+ * Gets cause for the error.
+ *
+ * @return the cause.
+ * @see java.lang.Throwable#getCause()
+ */
+ public Throwable getCause() {
+ return cause;
+ }
+
+ /* Documented in super interface */
+ public void addListener(RepaintRequestListener listener) {
+ }
+
+ /* Documented in super interface */
+ public void removeListener(RepaintRequestListener listener) {
+ }
+
+ /* Documented in super interface */
+ public void requestRepaint() {
+ }
+
+ /* Documented in super interface */
+ public void requestRepaintRequests() {
+ }
+
+ public String getDebugId() {
+ return null;
+ }
+
+ public void setDebugId(String id) {
+ throw new UnsupportedOperationException(
+ "Setting testing id for this Paintable is not implemented");
+ }
+
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal;
+
+/**
+ * Interface for different terminal types.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+public interface Terminal {
+
+ /**
+ * Gets the name of the default theme.
+ *
+ * @return the Name of the terminal window.
+ */
+ public String getDefaultTheme();
+
+ /**
+ * Gets the width of the terminal window in pixels.
+ *
+ * @return the Width of the terminal window.
+ */
+ public int getScreenWidth();
+
+ /**
+ * Gets the height of the terminal window in pixels.
+ *
+ * @return the Height of the terminal window.
+ */
+ public int getScreenHeight();
+
+ /**
+ * Terminal error event.
+ */
+ public interface ErrorEvent {
+
+ /**
+ * Gets the contained throwable.
+ */
+ public Throwable getThrowable();
+
+ }
+
+ /**
+ * Terminal error listener interface.
+ */
+ public interface ErrorListener {
+
+ /**
+ * Invoked when terminal error occurs.
+ *
+ * @param event
+ * the fired event.
+ */
+ public void terminalError(Terminal.ErrorEvent event);
+ }
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal;
+
+import com.itmill.toolkit.service.FileTypeResolver;
+
+/**
+ * <code>ThemeResource</code> is a named theme dependant resource provided and
+ * managed by a theme. The actual resource contents are dynamically resolved to
+ * comply with the used theme by the terminal adapter. This is commonly used to
+ * provide static images, flash, java-applets, etc for the terminals.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+public class ThemeResource implements Resource {
+
+ /**
+ * Id of the terminal managed resource.
+ */
+ private String resourceID = null;
+
+ /**
+ * Creates a resource.
+ *
+ * @param resourceId
+ * the Id of the resource.
+ */
+ public ThemeResource(String resourceId) {
+ if (resourceId == null) {
+ throw new NullPointerException("Resource ID must not be null");
+ }
+ if (resourceId.length() == 0) {
+ throw new IllegalArgumentException("Resource ID can not be empty");
+ }
+ if (resourceId.charAt(0) == '/') {
+ throw new IllegalArgumentException(
+ "Resource ID must be relative (can not begin with /)");
+ }
+
+ resourceID = resourceId;
+ }
+
+ /**
+ * Tests if the given object equals this Resource.
+ *
+ * @param obj
+ * the object to be tested for equality.
+ * @return <code>true</code> if the given object equals this Icon,
+ * <code>false</code> if not.
+ * @see java.lang.Object#equals(Object)
+ */
+ public boolean equals(Object obj) {
+ return obj instanceof ThemeResource
+ && resourceID.equals(((ThemeResource) obj).resourceID);
+ }
+
+ /**
+ * @see java.lang.Object#hashCode()
+ */
+ public int hashCode() {
+ return resourceID.hashCode();
+ }
+
+ /**
+ * @see java.lang.Object#toString()
+ */
+ public String toString() {
+ return resourceID.toString();
+ }
+
+ /**
+ * Gets the resource id.
+ *
+ * @return the resource id.
+ */
+ public String getResourceId() {
+ return resourceID;
+ }
+
+ /**
+ * @see com.itmill.toolkit.terminal.Resource#getMIMEType()
+ */
+ public String getMIMEType() {
+ return FileTypeResolver.getMIMEType(getResourceId());
+ }
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal;
+
+import java.net.URL;
+
+/**
+ * Interface implemented by all the classes capable of handling URI:s.
+ *
+ * <p>
+ * <code>URIHandler</code> can provide <code>DownloadStream</code> for
+ * transferring data for client.
+ * </p>
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+public interface URIHandler {
+
+ /**
+ * Handles a given relative URI. If the URI handling wants to emit a
+ * downloadable stream it can return download stream object. If no emitting
+ * stream is necessary, null should be returned instead.
+ *
+ * @param context
+ * the URl.
+ * @param relativeUri
+ * the relative uri.
+ * @return the download stream object.
+ */
+ public DownloadStream handleURI(URL context, String relativeUri);
+
+ /**
+ * URIHandler error event.
+ */
+ public interface ErrorEvent extends Terminal.ErrorEvent {
+
+ /**
+ * Gets the source URIHandler.
+ *
+ * @return the URIHandler.
+ */
+ public URIHandler getURIHandler();
+
+ }
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal;
+
+import java.io.InputStream;
+
+/**
+ * Defines a variable type, that is used for passing uploaded files from
+ * terminal. Most often, file upload is implented using the
+ * {@link com.itmill.toolkit.ui.Upload Upload} component.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+public interface UploadStream {
+
+ /**
+ * Gets the name of the stream.
+ *
+ * @return the name of the stream.
+ */
+ public String getStreamName();
+
+ /**
+ * Gets the input stream.
+ *
+ * @return the Input stream.
+ */
+ public InputStream getStream();
+
+ /**
+ * Gets the input stream content type.
+ *
+ * @return the content type of the input stream.
+ */
+ public String getContentType();
+
+ /**
+ * Gets stream content name. Stream content name usually differs from the
+ * actual stream name. It is used to identify the content of the stream.
+ *
+ * @return the Name of the stream content.
+ */
+ public String getContentName();
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal;
+
+/**
+ * <code>UserError</code> is a controlled error occurred in application. User
+ * errors are occur in normal usage of the application and guide the user.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+public class UserError implements ErrorMessage {
+
+ /**
+ * Content mode, where the error contains only plain text.
+ */
+ public static final int CONTENT_TEXT = 0;
+
+ /**
+ * Content mode, where the error contains preformatted text.
+ */
+ public static final int CONTENT_PREFORMATTED = 1;
+
+ /**
+ * Formatted content mode, where the contents is XML restricted to the UIDL
+ * 1.0 formatting markups.
+ */
+ public static final int CONTENT_UIDL = 2;
+
+ /**
+ * Content mode.
+ */
+ private int mode = CONTENT_TEXT;
+
+ /**
+ * Message in content mode.
+ */
+ private final String msg;
+
+ /**
+ * Error level.
+ */
+ private int level = ErrorMessage.ERROR;
+
+ /**
+ * Creates a textual error message of level ERROR.
+ *
+ * @param textErrorMessage
+ * the text of the error message.
+ */
+ public UserError(String textErrorMessage) {
+ msg = textErrorMessage;
+ }
+
+ /**
+ * Creates a error message with level and content mode.
+ *
+ * @param message
+ * the error message.
+ * @param contentMode
+ * the content Mode.
+ * @param errorLevel
+ * the level of error.
+ */
+ public UserError(String message, int contentMode, int errorLevel) {
+
+ // Check the parameters
+ if (contentMode < 0 || contentMode > 2) {
+ throw new java.lang.IllegalArgumentException(
+ "Unsupported content mode: " + contentMode);
+ }
+
+ msg = message;
+ mode = contentMode;
+ level = errorLevel;
+ }
+
+ /* Documenten in interface */
+ public int getErrorLevel() {
+ return level;
+ }
+
+ /* Documenten in interface */
+ public void addListener(RepaintRequestListener listener) {
+ }
+
+ /* Documenten in interface */
+ public void removeListener(RepaintRequestListener listener) {
+ }
+
+ /* Documenten in interface */
+ public void requestRepaint() {
+ }
+
+ /* Documenten in interface */
+ public void paint(PaintTarget target) throws PaintException {
+
+ target.startTag("error");
+
+ // Error level
+ if (level >= ErrorMessage.SYSTEMERROR) {
+ target.addAttribute("level", "system");
+ } else if (level >= ErrorMessage.CRITICAL) {
+ target.addAttribute("level", "critical");
+ } else if (level >= ErrorMessage.ERROR) {
+ target.addAttribute("level", "error");
+ } else if (level >= ErrorMessage.WARNING) {
+ target.addAttribute("level", "warning");
+ } else {
+ target.addAttribute("level", "info");
+ }
+
+ // Paint the message
+ switch (mode) {
+ case CONTENT_TEXT:
+ target.addText(msg);
+ break;
+ case CONTENT_UIDL:
+ target.addUIDL(msg);
+ break;
+ case CONTENT_PREFORMATTED:
+ target.startTag("pre");
+ target.addText(msg);
+ target.endTag("pre");
+ }
+
+ target.endTag("error");
+ }
+
+ /* Documenten in interface */
+ public void requestRepaintRequests() {
+ }
+
+ /* Documented in superclass */
+ public String toString() {
+ return msg;
+ }
+
+ public String getDebugId() {
+ return null;
+ }
+
+ public void setDebugId(String id) {
+ throw new UnsupportedOperationException(
+ "Setting testing id for this Paintable is not implemented");
+ }
+
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal;
+
+import java.util.Map;
+
+/**
+ * <p>
+ * Listener interface for UI variable changes. The user communicates with the
+ * application using the so-called <i>variables</i>. When the user makes a
+ * change using the UI the terminal trasmits the changed variables to the
+ * application, and the components owning those variables may then process those
+ * changes.
+ * </p>
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+public interface VariableOwner {
+
+ /**
+ * Called when one or more variables handled by the implementing class are
+ * changed.
+ *
+ * @param source
+ * the Source of the variable change. This is the origin of
+ * the event. For example in Web Adapter this is the request.
+ * @param variables
+ * the Mapping from variable names to new variable values.
+ */
+ public void changeVariables(Object source, Map variables);
+
+ /**
+ * <p>
+ * Tests if the variable owner is enabled or not. The terminal should not
+ * send any variable changes to disabled variable owners.
+ * </p>
+ *
+ * @return <code>true</code> if the variable owner is enabled,
+ * <code>false</code> if not
+ */
+ public boolean isEnabled();
+
+ /**
+ * <p>
+ * Tests if the variable owner is in immediate mode or not. Being in
+ * immediate mode means that all variable changes are required to be sent
+ * back from the terminal immediately when they occur.
+ * </p>
+ *
+ * <p>
+ * <strong>Note:</strong> <code>VariableOwner</code> does not include a
+ * set- method for the immediateness property. This is because not all
+ * VariableOwners wish to offer the functionality. Such VariableOwners are
+ * never in the immediate mode, thus they always return <code>false</code>
+ * in {@link #isImmediate()}.
+ * </p>
+ *
+ * @return <code>true</code> if the component is in immediate mode,
+ * <code>false</code> if not.
+ */
+ public boolean isImmediate();
+
+ /**
+ * VariableOwner error event.
+ */
+ public interface ErrorEvent extends Terminal.ErrorEvent {
+
+ /**
+ * Gets the source VariableOwner.
+ *
+ * @return the variable owner.
+ */
+ public VariableOwner getVariableOwner();
+
+ }
+}
--- /dev/null
+<module>
+ <source path="client"/>\r
+\r
+ <!-- This module just defines a entrypoint for the DefaultWidgetSet -->
+ <inherits name="com.itmill.toolkit.terminal.gwt.DefaultWidgetSetNoEntry"/>
+ <entry-point class="com.itmill.toolkit.terminal.gwt.client.DefaultWidgetSet"/>
+</module>
--- /dev/null
+<module>
+ <!-- \r
+ This is the NoEntry version of DefaultWidgetSet.\r
+ This is the module you want to extend when creating an extended\r
+ widget set.\r
+ -->\r
+
+ <inherits name="com.google.gwt.user.User"/>
+
+ <inherits name="com.google.gwt.http.HTTP"/>
+
+ <inherits name="com.google.gwt.xml.XML"/>
+
+ <inherits name="com.google.gwt.json.JSON"/>\r
+
+ <source path="client"/>
+
+ <source path="gwtwidgets"/>
+
+ <!-- \r
+ Default theme for this widget set. \r
+ Please name the sub directory differently when creating a extended widget set\r
+ (e.g. src="mywidgets/styles.css") to avoid naming conflicts.\r
+
+ <stylesheet src="default/common/common.css"/>
+ <stylesheet src="default/button/button.css"/>
+ <stylesheet src="default/textfield/textfield.css"/>
+ <stylesheet src="default/select/select.css"/>
+ <stylesheet src="default/panel/panel.css"/>
+ <stylesheet src="default/tabsheet/tabsheet.css"/>
+ <stylesheet src="default/datefield/datefield.css"/>
+ <stylesheet src="default/table/table.css"/>
+ <stylesheet src="default/slider/slider.css"/>
+ <stylesheet src="default/window/window.css"/>
+ <stylesheet src="default/window/notification.css"/>
+ <stylesheet src="default/caption/caption.css"/>
+ <stylesheet src="default/tree/tree.css"/>
+ <stylesheet src="default/splitpanel/splitpanel.css"/>
+ <stylesheet src="default/select/filterselect.css"/>
+ <stylesheet src="default/progressindicator/progressindicator.css"/>
+ <stylesheet src="default/expandlayout/expandlayout.css"/>
+ <stylesheet src="default/orderedlayout/orderedlayout.css"/>
+ <stylesheet src="default/accordion/accordion.css"/>
+ -->
+
+</module>
--- /dev/null
+package com.itmill.toolkit.terminal.gwt.client;
+
+import java.util.ArrayList;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class ApplicationConfiguration {
+
+ private String id;
+ private String themeUri;
+ private String pathInfo;
+ private String appUri;
+ private JavaScriptObject versionInfo;
+
+ public String getRootPanelId() {
+ return id;
+ }
+
+ public String getApplicationUri() {
+ return appUri;
+ }
+
+ public String getPathInfo() {
+ return pathInfo;
+ }
+
+ public String getThemeUri() {
+ return themeUri;
+ }
+
+ public void setAppId(String appId) {
+ id = appId;
+ }
+
+ public JavaScriptObject getVersionInfoJSObject() {
+ return versionInfo;
+ }
+
+ private native void loadFromDOM()
+ /*-{
+
+ var id = this.@com.itmill.toolkit.terminal.gwt.client.ApplicationConfiguration::id;
+ if($wnd.itmill.toolkitConfigurations && $wnd.itmill.toolkitConfigurations[id]) {
+ var jsobj = $wnd.itmill.toolkitConfigurations[id];
+ var uri = jsobj.appUri;
+ if(uri[uri.length -1] != "/") {
+ uri = uri + "/";
+ }
+ this.@com.itmill.toolkit.terminal.gwt.client.ApplicationConfiguration::appUri = uri;
+ this.@com.itmill.toolkit.terminal.gwt.client.ApplicationConfiguration::pathInfo = jsobj.pathInfo;
+ this.@com.itmill.toolkit.terminal.gwt.client.ApplicationConfiguration::themeUri = jsobj.themeUri;
+ if(jsobj.versionInfo) {
+ this.@com.itmill.toolkit.terminal.gwt.client.ApplicationConfiguration::versionInfo = jsobj.versionInfo;
+ }
+
+ } else {
+ $wnd.alert("Toolkit app failed to initialize: " + this.id);
+ }
+
+ }-*/;
+
+ public native static void loadAppIdListFromDOM(ArrayList list)
+ /*-{
+
+ var j;
+ for(j in $wnd.itmill.toolkitConfigurations) {
+ list.@java.util.Collection::add(Ljava/lang/Object;)(j);
+ }
+ }-*/;
+
+ public static ApplicationConfiguration getConfigFromDOM(String appId) {
+ ApplicationConfiguration conf = new ApplicationConfiguration();
+ conf.setAppId(appId);
+ conf.loadFromDOM();
+ return conf;
+ }
+
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Vector;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.http.client.Request;
+import com.google.gwt.http.client.RequestBuilder;
+import com.google.gwt.http.client.RequestCallback;
+import com.google.gwt.http.client.RequestException;
+import com.google.gwt.http.client.Response;
+import com.google.gwt.json.client.JSONArray;
+import com.google.gwt.json.client.JSONObject;
+import com.google.gwt.json.client.JSONParser;
+import com.google.gwt.json.client.JSONString;
+import com.google.gwt.json.client.JSONValue;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.DeferredCommand;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.WindowCloseListener;
+import com.google.gwt.user.client.ui.FocusWidget;
+import com.google.gwt.user.client.ui.HasFocus;
+import com.google.gwt.user.client.ui.HasWidgets;
+import com.google.gwt.user.client.ui.Widget;
+import com.itmill.toolkit.terminal.gwt.client.ui.ContextMenu;
+import com.itmill.toolkit.terminal.gwt.client.ui.Field;
+import com.itmill.toolkit.terminal.gwt.client.ui.IView;
+import com.itmill.toolkit.terminal.gwt.client.ui.Notification;
+import com.itmill.toolkit.terminal.gwt.client.ui.Notification.HideEvent;
+
+/**
+ * Entry point classes define <code>onModuleLoad()</code>.
+ */
+public class ApplicationConnection {
+ private static final String MODIFIED_CLASSNAME = "i-modified";
+
+ private static final String REQUIRED_CLASSNAME_EXT = "-required";
+
+ private static final String ERROR_CLASSNAME_EXT = "-error";
+
+ public static final String VAR_RECORD_SEPARATOR = "\u001e";
+
+ public static final String VAR_FIELD_SEPARATOR = "\u001f";
+
+ private final HashMap resourcesMap = new HashMap();
+
+ private static Console console;
+
+ private static boolean testingMode;
+
+ private final Vector pendingVariables = new Vector();
+
+ private final HashMap idToPaintable = new HashMap();
+
+ private final HashMap paintableToId = new HashMap();
+
+ /** Contains ExtendedTitleInfo by paintable id */
+ private final HashMap paintableToTitle = new HashMap();
+
+ private final WidgetSet widgetSet;
+
+ private ContextMenu contextMenu = null;
+
+ private Timer loadTimer;
+ private Timer loadTimer2;
+ private Timer loadTimer3;
+ private Element loadElement;
+
+ private final IView view;
+
+ private boolean applicationRunning = false;
+
+ /**
+ * True if each Paintable objects id is injected to DOM. Used for Testing
+ * Tools.
+ */
+ private boolean usePaintableIdsInDOM = false;
+
+ /**
+ * Contains reference for client wrapper given to Testing Tools.
+ *
+ * Used in JSNI functions
+ *
+ * @SuppressWarnings
+ */
+ private final JavaScriptObject ttClientWrapper = null;
+
+ private int activeRequests = 0;
+
+ private final ApplicationConfiguration configuration;
+
+ private final Vector pendingVariableBursts = new Vector();
+
+ public ApplicationConnection(WidgetSet widgetSet,
+ ApplicationConfiguration cnf) {
+ this.widgetSet = widgetSet;
+ configuration = cnf;
+
+ if (isDebugMode()) {
+ console = new DebugConsole(this);
+ } else {
+ console = new NullConsole();
+ }
+
+ if (checkTestingMode()) {
+ usePaintableIdsInDOM = true;
+ initializeTestingTools();
+ Window.addWindowCloseListener(new WindowCloseListener() {
+ public void onWindowClosed() {
+ uninitializeTestingTools();
+ }
+
+ public String onWindowClosing() {
+ return null;
+ }
+ });
+ }
+
+ initializeClientHooks();
+
+ // TODO remove hard coded id name
+ view = new IView(cnf.getRootPanelId());
+
+ makeUidlRequest("", true);
+ applicationRunning = true;
+ }
+
+ /**
+ * Method to check if application is in testing mode. Can be used after
+ * application init.
+ *
+ * @return true if in testing mode
+ */
+ public static boolean isTestingMode() {
+ return testingMode;
+ }
+
+ /**
+ * Check is application is run in testing mode.
+ *
+ * @return true if in testing mode
+ */
+ private native static boolean checkTestingMode()
+ /*-{
+ @com.itmill.toolkit.terminal.gwt.client.ApplicationConnection::testingMode = $wnd.top.itmill && $wnd.top.itmill.registerToTT ? true : false;
+ return @com.itmill.toolkit.terminal.gwt.client.ApplicationConnection::testingMode;
+ }-*/;
+
+ private native void initializeTestingTools()
+ /*-{
+ var ap = this;
+ var client = {};
+ client.isActive = function() {
+ return ap.@com.itmill.toolkit.terminal.gwt.client.ApplicationConnection::hasActiveRequest()();
+ }
+ var vi = ap.@com.itmill.toolkit.terminal.gwt.client.ApplicationConnection::getVersionInfo()();
+ if (vi) {
+ client.getVersionInfo = function() {
+ return vi;
+ }
+ }
+ $wnd.top.itmill.registerToTT(client);
+ this.@com.itmill.toolkit.terminal.gwt.client.ApplicationConnection::ttClientWrapper = client;
+ }-*/;
+
+ /**
+ * Helper for tt initialization
+ */
+ private JavaScriptObject getVersionInfo() {
+ return configuration.getVersionInfoJSObject();
+ }
+
+ private native void uninitializeTestingTools()
+ /*-{
+ $wnd.top.itmill.unregisterFromTT(this.@com.itmill.toolkit.terminal.gwt.client.ApplicationConnection::ttClientWrapper);
+ }-*/;
+
+ /**
+ * Publishes a JavaScript API for mash-up applications.
+ * <ul>
+ * <li><code>itmill.forceSync()</code> sends pending variable changes, in
+ * effect synchronizing the server and client state. This is done for all
+ * applications on host page.</li>
+ * </ul>
+ *
+ * TODO make this multi-app aware
+ */
+ private native void initializeClientHooks()
+ /*-{
+ var app = this;
+ var oldSync;
+ if($wnd.itmill.forceSync) {
+ oldSync = $wnd.itmill.forceSync;
+ }
+ $wnd.itmill.forceSync = function() {
+ if(oldSync) {
+ oldSync();
+ }
+ app.@com.itmill.toolkit.terminal.gwt.client.ApplicationConnection::sendPendingVariableChanges()();
+ }
+ }-*/;
+
+ public static Console getConsole() {
+ return console;
+ }
+
+ private native static boolean isDebugMode()
+ /*-{
+ var uri = $wnd.location;
+ var re = /debug[^\/]*$/;
+ return re.test(uri);
+ }-*/;
+
+ public String getAppUri() {
+ return configuration.getApplicationUri();
+ };
+
+ public boolean hasActiveRequest() {
+ return (activeRequests > 0);
+ }
+
+ private void makeUidlRequest(String requestData, boolean repaintAll) {
+ startRequest();
+
+ console.log("Making UIDL Request with params: " + requestData);
+ String uri = getAppUri() + "UIDL" + configuration.getPathInfo();
+ if (repaintAll) {
+ uri += "?repaintAll=1";
+ }
+ final RequestBuilder rb = new RequestBuilder(RequestBuilder.POST, uri);
+ // rb.setHeader("Content-Type",
+ // "application/x-www-form-urlencoded; charset=utf-8");
+ rb.setHeader("Content-Type", "text/plain;charset=utf-8");
+ try {
+ rb.sendRequest(requestData, new RequestCallback() {
+ public void onError(Request request, Throwable exception) {
+ // TODO Better reporting to user
+ console.error("Got error");
+ endRequest();
+ }
+
+ public void onResponseReceived(Request request,
+ Response response) {
+ handleReceivedJSONMessage(response);
+ }
+
+ });
+
+ } catch (final RequestException e) {
+ // TODO Better reporting to user
+ console.error(e.getMessage());
+ endRequest();
+ }
+ }
+
+ private void startRequest() {
+ activeRequests++;
+ showLoadingIndicator();
+ }
+
+ private void endRequest() {
+
+ checkForPendingVariableBursts();
+
+ activeRequests--;
+ // deferring to avoid flickering
+ DeferredCommand.addCommand(new Command() {
+ public void execute() {
+ if (activeRequests == 0) {
+ hideLoadingIndicator();
+ }
+ }
+ });
+ }
+
+ /**
+ * This method is called after applying uidl change set to application.
+ *
+ * It will clean current and queued variable change sets. And send next
+ * change set if it exists.
+ */
+ private void checkForPendingVariableBursts() {
+ cleanVariableBurst(pendingVariables);
+ if (pendingVariableBursts.size() > 0) {
+ for (Iterator iterator = pendingVariableBursts.iterator(); iterator
+ .hasNext();) {
+ cleanVariableBurst((Vector) iterator.next());
+ }
+ Vector nextBurst = (Vector) pendingVariableBursts.firstElement();
+ pendingVariableBursts.remove(0);
+ buildAndSendVariableBurst(nextBurst);
+ }
+ }
+
+ /**
+ * Cleans given queue of variable changes of such changes that came from
+ * components that do not exist anymore.
+ *
+ * @param variableBurst
+ */
+ private void cleanVariableBurst(Vector variableBurst) {
+ for (int i = 1; i < variableBurst.size(); i += 2) {
+ String id = (String) variableBurst.get(i);
+ id = id.substring(0, id.indexOf(VAR_FIELD_SEPARATOR));
+ if (!idToPaintable.containsKey(id)) {
+ // variable owner does not exist anymore
+ variableBurst.remove(i - 1);
+ variableBurst.remove(i - 1);
+ i -= 2;
+ ApplicationConnection.getConsole().log(
+ "Removed variable from removed component: " + id);
+ }
+ }
+ }
+
+ private void showLoadingIndicator() {
+ // show initial throbber
+ if (loadTimer == null) {
+ loadTimer = new Timer() {
+ public void run() {
+ // show initial throbber
+ if (loadElement == null) {
+ loadElement = DOM.createDiv();
+ DOM.setStyleAttribute(loadElement, "position",
+ "absolute");
+ DOM.appendChild(view.getElement(), loadElement);
+ ApplicationConnection.getConsole().log(
+ "inserting load indicator");
+ // Position
+ DOM.setStyleAttribute(loadElement, "top", (view
+ .getAbsoluteTop() + 6)
+ + "px");
+ }
+ DOM.setElementProperty(loadElement, "className",
+ "i-loading-indicator");
+ DOM.setStyleAttribute(loadElement, "display", "block");
+ DOM.setStyleAttribute(loadElement, "left", (view
+ .getAbsoluteLeft()
+ + view.getOffsetWidth()
+ - DOM.getElementPropertyInt(loadElement,
+ "offsetWidth") - 5)
+ + "px");
+
+ // Initialize other timers
+ loadTimer2 = new Timer() {
+ public void run() {
+ DOM.setElementProperty(loadElement, "className",
+ "i-loading-indicator-delay");
+ }
+ };
+ // Second one kicks in at 1500ms
+ loadTimer2.schedule(1200);
+
+ loadTimer3 = new Timer() {
+ public void run() {
+ DOM.setElementProperty(loadElement, "className",
+ "i-loading-indicator-wait");
+ }
+ };
+ // Third one kicks in at 5000ms
+ loadTimer3.schedule(4700);
+ }
+ };
+ // First one kicks in at 300ms
+ loadTimer.schedule(300);
+ }
+ }
+
+ private void hideLoadingIndicator() {
+ if (loadTimer != null) {
+ loadTimer.cancel();
+ if (loadTimer2 != null) {
+ loadTimer2.cancel();
+ loadTimer3.cancel();
+ }
+ loadTimer = null;
+ }
+ if (loadElement != null) {
+ DOM.removeChild(view.getElement(), loadElement);
+ loadElement = null;
+ }
+ }
+
+ private void handleReceivedJSONMessage(Response response) {
+ final Date start = new Date();
+ String jsonText = response.getText();
+ // for(;;);[realjson]
+ jsonText = jsonText.substring(9, jsonText.length() - 1);
+ JSONValue json;
+ try {
+ json = JSONParser.parse(jsonText);
+ } catch (final com.google.gwt.json.client.JSONException e) {
+ endRequest();
+ console.log(e.getMessage() + " - Original JSON-text:");
+ console.log(jsonText);
+ return;
+ }
+ // Handle redirect
+ final JSONObject redirect = (JSONObject) ((JSONObject) json)
+ .get("redirect");
+ if (redirect != null) {
+ final JSONString url = (JSONString) redirect.get("url");
+ if (url != null) {
+ console.log("redirecting to " + url.stringValue());
+ redirect(url.stringValue());
+ return;
+ }
+ }
+
+ // Store resources
+ final JSONObject resources = (JSONObject) ((JSONObject) json)
+ .get("resources");
+ for (final Iterator i = resources.keySet().iterator(); i.hasNext();) {
+ final String key = (String) i.next();
+ resourcesMap.put(key, ((JSONString) resources.get(key))
+ .stringValue());
+ }
+
+ // Store locale data
+ if (((JSONObject) json).containsKey("locales")) {
+ final JSONArray l = (JSONArray) ((JSONObject) json).get("locales");
+ for (int i = 0; i < l.size(); i++) {
+ LocaleService.addLocale((JSONObject) l.get(i));
+ }
+ }
+
+ JSONObject meta = null;
+ if (((JSONObject) json).containsKey("meta")) {
+ meta = ((JSONObject) json).get("meta").isObject();
+ if (meta.containsKey("repaintAll")) {
+ view.clear();
+ idToPaintable.clear();
+ paintableToId.clear();
+ }
+ }
+
+ // Process changes
+ final JSONArray changes = (JSONArray) ((JSONObject) json)
+ .get("changes");
+ for (int i = 0; i < changes.size(); i++) {
+ try {
+ final UIDL change = new UIDL((JSONArray) changes.get(i));
+ try {
+ console.dirUIDL(change);
+ } catch (final Exception e) {
+ console.log(e.getMessage());
+ // TODO: dir doesn't work in any browser although it should
+ // work (works in hosted mode)
+ // it partially did at some part but now broken.
+ }
+ final UIDL uidl = change.getChildUIDL(0);
+ final Paintable paintable = getPaintable(uidl.getId());
+ if (paintable != null) {
+ paintable.updateFromUIDL(uidl, this);
+ } else {
+ if (!uidl.getTag().equals("window")) {
+ System.out.println("Received update for "
+ + uidl.getTag()
+ + ", but there is no such paintable ("
+ + uidl.getId() + ") rendered.");
+ } else {
+ view.updateFromUIDL(uidl, this);
+ }
+ }
+ } catch (final Throwable e) {
+ e.printStackTrace();
+ }
+ }
+
+ if (meta != null) {
+ if (meta.containsKey("focus")) {
+ final String focusPid = meta.get("focus").isString()
+ .stringValue();
+ final Paintable toBeFocused = getPaintable(focusPid);
+ if (toBeFocused instanceof HasFocus) {
+ final HasFocus toBeFocusedWidget = (HasFocus) toBeFocused;
+ toBeFocusedWidget.setFocus(true);
+ } else if (toBeFocused instanceof Focusable) {
+ ((Focusable) toBeFocused).focus();
+ } else {
+ getConsole().log("Could not focus component");
+ }
+
+ }
+ if (meta.containsKey("appError")) {
+ JSONObject error = meta.get("appError").isObject();
+ JSONValue val = error.get("caption");
+ String html = "";
+ if (val.isString() != null) {
+ html += "<h1>" + val.isString().stringValue() + "</h1>";
+ }
+ val = error.get("message");
+ if (val.isString() != null) {
+ html += "<p>" + val.isString().stringValue() + "</p>";
+ }
+ val = error.get("url");
+ String url = null;
+ if (val.isString() != null) {
+ url = val.isString().stringValue();
+ }
+
+ if (html.length() != 0) {
+ Notification n = new Notification(1000 * 60 * 45); // 45min
+ n.addEventListener(new NotificationRedirect(url));
+ n.show(html, Notification.CENTERED_TOP, "system");
+ } else {
+ redirect(url);
+ }
+ applicationRunning = false;
+ }
+ }
+
+ final long prosessingTime = (new Date().getTime()) - start.getTime();
+ console.log(" Processing time was " + String.valueOf(prosessingTime)
+ + "ms for " + jsonText.length() + " characters of JSON");
+ console.log("Referenced paintables: " + idToPaintable.size());
+
+ endRequest();
+ }
+
+ // Redirect browser, null reloads current page
+ private static native void redirect(String url)
+ /*-{
+ if (url) {
+ $wnd.location = url;
+ } else {
+ $wnd.location = $wnd.location;
+ }
+ }-*/;
+
+ public void registerPaintable(String id, Paintable paintable) {
+ idToPaintable.put(id, paintable);
+ paintableToId.put(paintable, id);
+ }
+
+ public void unregisterPaintable(Paintable p) {
+ Object id = paintableToId.get(p);
+ idToPaintable.remove(id);
+ paintableToTitle.remove(id);
+ paintableToId.remove(p);
+
+ if (p instanceof HasWidgets) {
+ unregisterChildPaintables((HasWidgets) p);
+ }
+ }
+
+ public void unregisterChildPaintables(HasWidgets container) {
+ final Iterator it = container.iterator();
+ while (it.hasNext()) {
+ final Widget w = (Widget) it.next();
+ if (w instanceof Paintable) {
+ unregisterPaintable((Paintable) w);
+ } else if (w instanceof HasWidgets) {
+ unregisterChildPaintables((HasWidgets) w);
+ }
+ }
+ }
+
+ /**
+ * Returns Paintable element by its id
+ *
+ * @param id
+ * Paintable ID
+ */
+ public Paintable getPaintable(String id) {
+ return (Paintable) idToPaintable.get(id);
+ }
+
+ private void addVariableToQueue(String paintableId, String variableName,
+ String encodedValue, boolean immediate, char type) {
+ final String id = paintableId + VAR_FIELD_SEPARATOR + variableName
+ + VAR_FIELD_SEPARATOR + type;
+ for (int i = 1; i < pendingVariables.size(); i += 2) {
+ if ((pendingVariables.get(i)).equals(id)) {
+ pendingVariables.remove(i - 1);
+ pendingVariables.remove(i - 1);
+ break;
+ }
+ }
+ pendingVariables.add(encodedValue);
+ pendingVariables.add(id);
+ if (immediate) {
+ sendPendingVariableChanges();
+ }
+ }
+
+ /**
+ * This method sends currently queued variable changes to server. It is
+ * called when immediate variable update must happen.
+ *
+ * To ensure correct order for variable changes (due servers multithreading
+ * or network), we always wait for active request to be handler before
+ * sending a new one. If there is an active request, we will put varible
+ * "burst" to queue that will be purged after current request is handled.
+ *
+ */
+ public void sendPendingVariableChanges() {
+ if (applicationRunning) {
+ if (hasActiveRequest()) {
+ // skip empty queues if there are pending bursts to be sent
+ if (pendingVariables.size() > 0
+ || pendingVariableBursts.size() == 0) {
+ Vector burst = (Vector) pendingVariables.clone();
+ pendingVariableBursts.add(burst);
+ }
+ } else {
+ buildAndSendVariableBurst(pendingVariables);
+ }
+ }
+ }
+
+ private void buildAndSendVariableBurst(Vector pendingVariables) {
+ final StringBuffer req = new StringBuffer();
+
+ for (int i = 0; i < pendingVariables.size(); i++) {
+ if (i > 0) {
+ if (i % 2 == 0) {
+ req.append(VAR_RECORD_SEPARATOR);
+ } else {
+ req.append(VAR_FIELD_SEPARATOR);
+ }
+ }
+ req.append(pendingVariables.get(i));
+ }
+
+ pendingVariables.clear();
+ makeUidlRequest(req.toString(), false);
+ }
+
+ public void updateVariable(String paintableId, String variableName,
+ String newValue, boolean immediate) {
+ addVariableToQueue(paintableId, variableName, newValue, immediate, 's');
+ }
+
+ public void updateVariable(String paintableId, String variableName,
+ int newValue, boolean immediate) {
+ addVariableToQueue(paintableId, variableName, "" + newValue, immediate,
+ 'i');
+ }
+
+ public void updateVariable(String paintableId, String variableName,
+ long newValue, boolean immediate) {
+ addVariableToQueue(paintableId, variableName, "" + newValue, immediate,
+ 'l');
+ }
+
+ public void updateVariable(String paintableId, String variableName,
+ float newValue, boolean immediate) {
+ addVariableToQueue(paintableId, variableName, "" + newValue, immediate,
+ 'f');
+ }
+
+ public void updateVariable(String paintableId, String variableName,
+ double newValue, boolean immediate) {
+ addVariableToQueue(paintableId, variableName, "" + newValue, immediate,
+ 'd');
+ }
+
+ public void updateVariable(String paintableId, String variableName,
+ boolean newValue, boolean immediate) {
+ addVariableToQueue(paintableId, variableName, newValue ? "true"
+ : "false", immediate, 'b');
+ }
+
+ public void updateVariable(String paintableId, String variableName,
+ Object[] values, boolean immediate) {
+ final StringBuffer buf = new StringBuffer();
+ for (int i = 0; i < values.length; i++) {
+ if (i > 0) {
+ buf.append(",");
+ }
+ buf.append(values[i].toString());
+ }
+ addVariableToQueue(paintableId, variableName, buf.toString(),
+ immediate, 'a');
+ }
+
+ /**
+ * Update generic component features.
+ *
+ * <h2>Selecting correct implementation</h2>
+ *
+ * <p>
+ * The implementation of a component depends on many properties, including
+ * styles, component features, etc. Sometimes the user changes those
+ * properties after the component has been created. Calling this method in
+ * the beginning of your updateFromUIDL -method automatically replaces your
+ * component with more appropriate if the requested implementation changes.
+ * </p>
+ *
+ * <h2>Caption, icon, error messages and description</h2>
+ *
+ * <p>
+ * Component can delegate management of caption, icon, error messages and
+ * description to parent layout. This is optional an should be decided by
+ * component author
+ * </p>
+ *
+ * <h2>Component visibility and disabling</h2>
+ *
+ * This method will manage component visibility automatically and if
+ * component is an instanceof FocusWidget, also handle component disabling
+ * when needed.
+ *
+ * @param component
+ * Widget to be updated, expected to implement an instance of
+ * Paintable
+ * @param uidl
+ * UIDL to be painted
+ * @param manageCaption
+ * True if you want to delegate caption, icon, description
+ * and error message management to parent.
+ *
+ * @return Returns true iff no further painting is needed by caller
+ */
+ public boolean updateComponent(Widget component, UIDL uidl,
+ boolean manageCaption) {
+
+ // If the server request that a cached instance should be used, do
+ // nothing
+ if (uidl.getBooleanAttribute("cached")) {
+ return true;
+ }
+
+ // Visibility
+ boolean visible = !uidl.getBooleanAttribute("invisible");
+ component.setVisible(visible);
+ // Set captions
+ if (manageCaption) {
+ final Container parent = Util.getParentLayout(component);
+ if (parent != null) {
+ parent.updateCaption((Paintable) component, uidl);
+ }
+ }
+
+ if (!visible) {
+ return true;
+ }
+
+ // Switch to correct implementation if needed
+ if (!widgetSet.isCorrectImplementation(component, uidl)) {
+ final Container parent = Util.getParentLayout(component);
+ if (parent != null) {
+ final Widget w = widgetSet.createWidget(uidl);
+ parent.replaceChildComponent(component, w);
+ registerPaintable(uidl.getId(), (Paintable) w);
+ ((Paintable) w).updateFromUIDL(uidl, this);
+ return true;
+ }
+ }
+
+ updateComponentSize(component, uidl);
+
+ boolean enabled = !uidl.getBooleanAttribute("disabled");
+ if (component instanceof FocusWidget) {
+ ((FocusWidget) component).setEnabled(enabled);
+ }
+
+ StringBuffer styleBuf = new StringBuffer();
+ final String primaryName = component.getStylePrimaryName();
+ styleBuf.append(primaryName);
+
+ // first disabling and read-only status
+ if (!enabled) {
+ styleBuf.append(" ");
+ styleBuf.append("i-disabled");
+ }
+ if (uidl.getBooleanAttribute("readonly")) {
+ styleBuf.append(" ");
+ styleBuf.append("i-readonly");
+ }
+
+ // add additional styles as css classes, prefixed with component default
+ // stylename
+ if (uidl.hasAttribute("style")) {
+ final String[] styles = uidl.getStringAttribute("style").split(" ");
+ for (int i = 0; i < styles.length; i++) {
+ styleBuf.append(" ");
+ styleBuf.append(primaryName);
+ styleBuf.append("-");
+ styleBuf.append(styles[i]);
+ }
+ }
+
+ // add modified classname to Fields
+ if (uidl.hasAttribute("modified") && component instanceof Field) {
+ styleBuf.append(" ");
+ styleBuf.append(MODIFIED_CLASSNAME);
+ }
+
+ TooltipInfo tooltipInfo = getTitleInfo((Paintable) component);
+ if (uidl.hasAttribute("description")) {
+ tooltipInfo.setTitle(uidl.getStringAttribute("description"));
+ } else {
+ tooltipInfo.setTitle(null);
+ }
+
+ // add error classname to components w/ error
+ if (uidl.hasAttribute("error")) {
+ styleBuf.append(" ");
+ styleBuf.append(primaryName);
+ styleBuf.append(ERROR_CLASSNAME_EXT);
+
+ tooltipInfo.setErrorUidl(uidl.getErrors());
+ } else {
+ tooltipInfo.setErrorUidl(null);
+ }
+
+ // add required style to required components
+ if (uidl.hasAttribute("required")) {
+ styleBuf.append(" ");
+ styleBuf.append(primaryName);
+ styleBuf.append(REQUIRED_CLASSNAME_EXT);
+ }
+
+ // Styles + disabled & readonly
+ component.setStyleName(styleBuf.toString());
+
+ if (usePaintableIdsInDOM) {
+ DOM.setElementProperty(component.getElement(), "id", uidl.getId());
+ }
+
+ return false;
+ }
+
+ private void updateComponentSize(Widget component, UIDL uidl) {
+ String w = uidl.hasAttribute("width") ? uidl
+ .getStringAttribute("width") : "";
+ component.setWidth(w);
+ String h = uidl.hasAttribute("height") ? uidl
+ .getStringAttribute("height") : "";
+ component.setHeight(h);
+ }
+
+ /**
+ * Get either existing or new Paintable for given UIDL.
+ *
+ * If corresponding Paintable has been previously painted, return it.
+ * Otherwise create and register a new Paintable from UIDL. Caller must
+ * update the returned Paintable from UIDL after it has been connected to
+ * parent.
+ *
+ * @param uidl
+ * UIDL to create Paintable from.
+ * @return Either existing or new Paintable corresponding to UIDL.
+ */
+ public Paintable getPaintable(UIDL uidl) {
+ final String id = uidl.getId();
+ Paintable w = getPaintable(id);
+ if (w != null) {
+ return w;
+ }
+ w = (Paintable) widgetSet.createWidget(uidl);
+ registerPaintable(id, w);
+ return w;
+ }
+
+ public String getResource(String name) {
+ return (String) resourcesMap.get(name);
+ }
+
+ /**
+ * Singleton method to get instance of app's context menu.
+ *
+ * @return IContextMenu object
+ */
+ public ContextMenu getContextMenu() {
+ if (contextMenu == null) {
+ contextMenu = new ContextMenu();
+ if (usePaintableIdsInDOM) {
+ DOM.setElementProperty(contextMenu.getElement(), "id",
+ "PID_TOOLKIT_CM");
+ }
+ }
+ return contextMenu;
+ }
+
+ /**
+ * Translates custom protocols in UIRL URI's to be recognizable by browser.
+ * All uri's from UIDL should be routed via this method before giving them
+ * to browser due URI's in UIDL may contain custom protocols like theme://.
+ *
+ * @param toolkitUri
+ * toolkit URI from uidl
+ * @return translated URI ready for browser
+ */
+ public String translateToolkitUri(String toolkitUri) {
+ if (toolkitUri == null) {
+ return null;
+ }
+ if (toolkitUri.startsWith("theme://")) {
+ final String themeUri = configuration.getThemeUri();
+ if (themeUri == null) {
+ console
+ .error("Theme not set: ThemeResource will not be found. ("
+ + toolkitUri + ")");
+ }
+ toolkitUri = themeUri + toolkitUri.substring(7);
+ }
+ return toolkitUri;
+ }
+
+ public String getThemeUri() {
+ return configuration.getThemeUri();
+ }
+
+ /**
+ * Listens for Notification hide event, and redirects. Used for system
+ * messages, such as session expired.
+ *
+ */
+ private class NotificationRedirect implements Notification.EventListener {
+ String url;
+
+ NotificationRedirect(String url) {
+ this.url = url;
+ }
+
+ public void notificationHidden(HideEvent event) {
+ redirect(url);
+ }
+
+ }
+
+ /* Extended title handling */
+
+ /**
+ * Data showed in tooltips are stored centrilized as it may be needed in
+ * varios place: caption, layouts, and in owner components themselves.
+ *
+ * Updating TooltipInfo is done in updateComponent method.
+ *
+ */
+ public TooltipInfo getTitleInfo(Paintable titleOwner) {
+ TooltipInfo info = (TooltipInfo) paintableToTitle.get(titleOwner);
+ if (info == null) {
+ info = new TooltipInfo();
+ paintableToTitle.put(titleOwner, info);
+ }
+ return info;
+ }
+
+ private final Tooltip tooltip = new Tooltip(this);
+
+ /**
+ * Component may want to delegate Tooltip handling to client. Layouts add
+ * Tooltip (description, errors) to caption, but some components may want
+ * them to appear one other elements too.
+ *
+ * Events wanted by this handler are same as in Tooltip.TOOLTIP_EVENTS
+ *
+ * @param event
+ * @param owner
+ */
+ public void handleTooltipEvent(Event event, Paintable owner) {
+ tooltip.handleTooltipEvent(event, owner);
+
+ }
+
+ /**
+ * Adds PNG-fix conditionally (only for IE6) to the specified IMG -element.
+ *
+ * @param el
+ * the IMG element to fix
+ */
+ public void addPngFix(Element el) {
+ BrowserInfo b = BrowserInfo.get();
+ if (b.isIE6()) {
+ Util.addPngFix(el, getThemeUri()
+ + "/../default/common/img/blank.gif");
+ }
+ }
+
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client;
+
+/**
+ * Class used to query information about web browser.
+ *
+ * Browser details are detected only once and those are stored in this singleton
+ * class.
+ *
+ */
+public class BrowserInfo {
+
+ private static BrowserInfo instance;
+
+ /**
+ * Singleton method to get BrowserInfo object.
+ *
+ * @return instance of BrowserInfo object
+ */
+ public static BrowserInfo get() {
+ if (instance == null) {
+ instance = new BrowserInfo();
+ }
+ return instance;
+ }
+
+ private boolean isGecko;
+ private boolean isAppleWebKit;
+ private boolean isSafari;
+ private boolean isOpera;
+ private boolean isIE;
+ private float ieVersion = -1;
+ private float geckoVersion = -1;
+ private float appleWebKitVersion = -1;
+
+ private BrowserInfo() {
+ try {
+ String ua = getBrowserString().toLowerCase();
+ // browser engine name
+ isGecko = ua.indexOf("gecko") != -1 && ua.indexOf("safari") == -1;
+ isAppleWebKit = ua.indexOf("applewebkit") != -1;
+
+ // browser name
+ isSafari = ua.indexOf("safari") != -1;
+ isOpera = ua.indexOf("opera") != -1;
+ isIE = ua.indexOf("msie") != -1 && !isOpera
+ && (ua.indexOf("webtv") == -1);
+
+ if (isGecko) {
+ String tmp = ua.substring(ua.indexOf("rv:") + 3);
+ tmp = tmp.replaceFirst("(\\.[0-9]+).+", "$1");
+ geckoVersion = Float.parseFloat(tmp);
+ }
+ if (isAppleWebKit) {
+ String tmp = ua.substring(ua.indexOf("webkit/") + 7);
+ tmp = tmp.replaceFirst("([0-9]+)[^0-9].+", "$1");
+ appleWebKitVersion = Float.parseFloat(tmp);
+
+ }
+
+ if (isIE) {
+ String ieVersionString = ua.substring(ua.indexOf("msie ") + 5);
+ ieVersionString = ieVersionString.substring(0, ieVersionString
+ .indexOf(";"));
+ ieVersion = Float.parseFloat(ieVersionString);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ ApplicationConnection.getConsole().error(e.getMessage());
+ }
+ }
+
+ public boolean isIE() {
+ return isIE;
+ }
+
+ public boolean isSafari() {
+ return isSafari;
+ }
+
+ public boolean isIE6() {
+ return isIE && ieVersion == 6;
+ }
+
+ public boolean isIE7() {
+ return isIE && ieVersion == 7;
+ }
+
+ public boolean isGecko() {
+ return isGecko;
+ }
+
+ public boolean isFF2() {
+ return isGecko && geckoVersion == 1.8;
+ }
+
+ public boolean isFF3() {
+ return isGecko && geckoVersion == 1.9;
+ }
+
+ public float getGeckoVersion() {
+ return geckoVersion;
+ }
+
+ public float getWebkitVersion() {
+ return appleWebKitVersion;
+ }
+
+ public float getIEVersion() {
+ return ieVersion;
+ }
+
+ public boolean isOpera() {
+ return isOpera;
+ }
+
+ public native static String getBrowserString()
+ /*-{
+ return $wnd.navigator.userAgent;
+ }-*/;
+
+ public static void test() {
+ Console c = ApplicationConnection.getConsole();
+
+ c.log("getBrowserString() " + getBrowserString());
+ c.log("isIE() " + get().isIE());
+ c.log("isIE6() " + get().isIE6());
+ c.log("isIE7() " + get().isIE7());
+ c.log("isFF2() " + get().isFF2());
+ c.log("isSafari() " + get().isSafari());
+ c.log("getGeckoVersion() " + get().getGeckoVersion());
+ c.log("getWebkitVersion() " + get().getWebkitVersion());
+ c.log("getIEVersion() " + get().getIEVersion());
+ c.log("isIE() " + get().isIE());
+ c.log("isIE() " + get().isIE());
+ }
+
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client;
+
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.HTML;
+import com.itmill.toolkit.terminal.gwt.client.ui.Icon;
+
+public class Caption extends HTML {
+
+ public static final String CLASSNAME = "i-caption";
+
+ private final Paintable owner;
+
+ private Element errorIndicatorElement;
+
+ private Element requiredFieldIndicator;
+
+ private Icon icon;
+
+ private Element captionText;
+
+ private final ApplicationConnection client;
+
+ /**
+ *
+ * @param component
+ * optional owner of caption. If not set, getOwner will
+ * return null
+ * @param client
+ */
+ public Caption(Paintable component, ApplicationConnection client) {
+ super();
+ this.client = client;
+ owner = component;
+ setStyleName(CLASSNAME);
+ }
+
+ public void updateCaption(UIDL uidl) {
+ setVisible(!uidl.getBooleanAttribute("invisible"));
+
+ setStyleName(getElement(), "i-disabled", uidl.hasAttribute("disabled"));
+
+ boolean isEmpty = true;
+
+ if (uidl.hasAttribute("icon")) {
+ if (icon == null) {
+ icon = new Icon(client);
+
+ DOM.insertChild(getElement(), icon.getElement(), 0);
+ }
+ icon.setUri(uidl.getStringAttribute("icon"));
+ isEmpty = false;
+ } else {
+ if (icon != null) {
+ DOM.removeChild(getElement(), icon.getElement());
+ icon = null;
+ }
+ }
+
+ if (uidl.hasAttribute("caption")) {
+ if (captionText == null) {
+ captionText = DOM.createSpan();
+ DOM
+ .insertChild(getElement(), captionText,
+ icon == null ? 0 : 1);
+ }
+ String c = uidl.getStringAttribute("caption");
+ if (c == null) {
+ c = "";
+ } else {
+ isEmpty = false;
+ }
+ DOM.setInnerText(captionText, c);
+ } else {
+ // TODO should span also be removed
+ }
+
+ if (uidl.hasAttribute("description")) {
+ if (captionText != null) {
+ addStyleDependentName("hasdescription");
+ } else {
+ removeStyleDependentName("hasdescription");
+ }
+ }
+
+ if (uidl.getBooleanAttribute("required")) {
+ isEmpty = false;
+ if (requiredFieldIndicator == null) {
+ requiredFieldIndicator = DOM.createSpan();
+ DOM.setInnerText(requiredFieldIndicator, "*");
+ DOM.setElementProperty(requiredFieldIndicator, "className",
+ "i-required-field-indicator");
+ DOM.appendChild(getElement(), requiredFieldIndicator);
+ }
+ } else {
+ if (requiredFieldIndicator != null) {
+ DOM.removeChild(getElement(), requiredFieldIndicator);
+ requiredFieldIndicator = null;
+ }
+ }
+
+ if (uidl.hasAttribute("error")) {
+ isEmpty = false;
+ if (errorIndicatorElement == null) {
+ errorIndicatorElement = DOM.createDiv();
+ if (Util.isIE()) {
+ DOM.setInnerHTML(errorIndicatorElement, " ");
+ }
+ DOM.setElementProperty(errorIndicatorElement, "className",
+ "i-errorindicator");
+ DOM.appendChild(getElement(), errorIndicatorElement);
+ }
+ } else if (errorIndicatorElement != null) {
+ DOM.removeChild(getElement(), errorIndicatorElement);
+ errorIndicatorElement = null;
+ }
+
+ // Workaround for IE weirdness, sometimes returns bad height in some
+ // circumstances when Caption is empty. See #1444
+ // IE7 bugs more often. I wonder what happens when IE8 arrives...
+ if (Util.isIE()) {
+ if (isEmpty) {
+ setHeight("0px");
+ DOM.setStyleAttribute(getElement(), "overflow", "hidden");
+ } else {
+ setHeight("");
+ DOM.setStyleAttribute(getElement(), "overflow", "");
+ }
+
+ }
+
+ }
+
+ public void onBrowserEvent(Event event) {
+ super.onBrowserEvent(event);
+ final Element target = DOM.eventGetTarget(event);
+ if (client != null && !DOM.compare(target, getElement())) {
+ client.handleTooltipEvent(event, owner);
+ }
+ }
+
+ public static boolean isNeeded(UIDL uidl) {
+ if (uidl.getStringAttribute("caption") != null) {
+ return true;
+ }
+ if (uidl.hasAttribute("error")) {
+ return true;
+ }
+ if (uidl.hasAttribute("icon")) {
+ return true;
+ }
+ if (uidl.hasAttribute("required")) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns Paintable for which this Caption belongs to.
+ *
+ * @return owner Widget
+ */
+ public Paintable getOwner() {
+ return owner;
+ }
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client;
+
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.Widget;
+
+public class CaptionWrapper extends FlowPanel {
+
+ public static final String CLASSNAME = "i-captionwrapper";
+ Caption caption;
+ Paintable widget;
+
+ public CaptionWrapper(Paintable toBeWrapped, ApplicationConnection client) {
+ caption = new Caption(toBeWrapped, client);
+ add(caption);
+ widget = toBeWrapped;
+ add((Widget) widget);
+ setStyleName(CLASSNAME);
+ }
+
+ public void updateCaption(UIDL uidl) {
+ caption.updateCaption(uidl);
+ setVisible(!uidl.getBooleanAttribute("invisible"));
+ }
+
+ public Paintable getPaintable() {
+ return widget;
+ }
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client;
+
+public interface Console {
+
+ public abstract void log(String msg);
+
+ public abstract void error(String msg);
+
+ public abstract void printObject(Object msg);
+
+ public abstract void dirUIDL(UIDL u);
+
+}
\ No newline at end of file
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client;
+
+import com.google.gwt.user.client.ui.Widget;
+
+public interface Container extends Paintable {
+
+ /**
+ * Replace child of this layout with another component.
+ *
+ * Each layout must be able to switch children. To to this, one must just
+ * give references to a current and new child. Note that the Layout is not
+ * responsible for registering Paintable into ApplicationConnection, but it
+ * is responsible is for unregistering it.
+ *
+ * @param oldComponent
+ * Child to be replaced
+ * @param newComponent
+ * Child that replaces the oldComponent
+ */
+ void replaceChildComponent(Widget oldComponent, Widget newComponent);
+
+ /**
+ * Is a given component child of this layout.
+ *
+ * @param component
+ * Component to test.
+ * @return true iff component is a child of this layout.
+ */
+ boolean hasChildComponent(Widget component);
+
+ /**
+ * Update child components caption, description and error message.
+ *
+ * <p>
+ * Each component is responsible for maintaining its caption, description
+ * and error message. In most cases components doesn't want to do that and
+ * those elements reside outside of the component. Because of this layouts
+ * must provide service for it's childen to show those elements for them.
+ * </p>
+ *
+ * @param component
+ * Child component for which service is requested.
+ * @param uidl
+ * UIDL of the child component.
+ */
+ void updateCaption(Paintable component, UIDL uidl);
+
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client;
+
+/**
+ * ContainerResizedListener interface is useful for Widgets that support
+ * relative sizes and who need some additional sizing logic.
+ */
+public interface ContainerResizedListener {
+ /**
+ * This function is run when container box has been resized. Object
+ * implementing ContainerResizedListener is responsible to call the same
+ * function on its ancestors that implement NeedsLayout in case their
+ * container has resized. runAnchestorsLayout(HasWidgets parent) function
+ * from Util class may be a good helper for this.
+ */
+ public void iLayout();
+}
--- /dev/null
+/* \r
+@ITMillApache2LicenseForJavaFiles@\r
+ */\r
+\r
+package com.itmill.toolkit.terminal.gwt.client;\r
+\r
+import java.util.Date;\r
+\r
+/**\r
+ * This class provides date/time parsing services to all components on the\r
+ * client side.\r
+ * \r
+ * @author IT Mill Ltd.\r
+ * \r
+ */\r
+public class DateTimeService {\r
+ public static int RESOLUTION_YEAR = 0;\r
+ public static int RESOLUTION_MONTH = 1;\r
+ public static int RESOLUTION_DAY = 2;\r
+ public static int RESOLUTION_HOUR = 3;\r
+ public static int RESOLUTION_MIN = 4;\r
+ public static int RESOLUTION_SEC = 5;\r
+ public static int RESOLUTION_MSEC = 6;\r
+\r
+ private String currentLocale;\r
+\r
+ private static int[] maxDaysInMonth = { 31, 28, 31, 30, 31, 30, 31, 31, 30,\r
+ 31, 30, 31 };\r
+\r
+ /**\r
+ * Creates a new date time service with the application default locale.\r
+ */\r
+ public DateTimeService() {\r
+ currentLocale = LocaleService.getDefaultLocale();\r
+ }\r
+\r
+ /**\r
+ * Creates a new date time service with a given locale.\r
+ * \r
+ * @param locale\r
+ * e.g. fi, en etc.\r
+ * @throws LocaleNotLoadedException\r
+ */\r
+ public DateTimeService(String locale) throws LocaleNotLoadedException {\r
+ setLocale(locale);\r
+ }\r
+\r
+ public void setLocale(String locale) throws LocaleNotLoadedException {\r
+ if (LocaleService.getAvailableLocales().contains(locale)) {\r
+ currentLocale = locale;\r
+ } else {\r
+ throw new LocaleNotLoadedException(locale);\r
+ }\r
+ }\r
+\r
+ public String getLocale() {\r
+ return currentLocale;\r
+ }\r
+\r
+ public String getMonth(int month) {\r
+ try {\r
+ return LocaleService.getMonthNames(currentLocale)[month];\r
+ } catch (final LocaleNotLoadedException e) {\r
+ // TODO redirect to console\r
+ System.out.println(e + ":" + e.getMessage());\r
+ }\r
+ return null;\r
+ }\r
+\r
+ public String getShortMonth(int month) {\r
+ try {\r
+ return LocaleService.getShortMonthNames(currentLocale)[month];\r
+ } catch (final LocaleNotLoadedException e) {\r
+ // TODO redirect to console\r
+ System.out.println(e + ":" + e.getMessage());\r
+ }\r
+ return null;\r
+ }\r
+\r
+ public String getDay(int day) {\r
+ try {\r
+ return LocaleService.getDayNames(currentLocale)[day];\r
+ } catch (final LocaleNotLoadedException e) {\r
+ // TODO redirect to console\r
+ System.out.println(e + ":" + e.getMessage());\r
+ }\r
+ return null;\r
+ }\r
+\r
+ public String getShortDay(int day) {\r
+ try {\r
+ return LocaleService.getShortDayNames(currentLocale)[day];\r
+ } catch (final LocaleNotLoadedException e) {\r
+ // TODO redirect to console\r
+ System.out.println(e + ":" + e.getMessage());\r
+ }\r
+ return null;\r
+ }\r
+\r
+ public int getFirstDayOfWeek() {\r
+ try {\r
+ return LocaleService.getFirstDayOfWeek(currentLocale);\r
+ } catch (final LocaleNotLoadedException e) {\r
+ // TODO redirect to console\r
+ System.out.println(e + ":" + e.getMessage());\r
+ }\r
+ return 0;\r
+ }\r
+\r
+ public boolean isTwelveHourClock() {\r
+ try {\r
+ return LocaleService.isTwelveHourClock(currentLocale);\r
+ } catch (final LocaleNotLoadedException e) {\r
+ // TODO redirect to console\r
+ System.out.println(e + ":" + e.getMessage());\r
+ }\r
+ return false;\r
+ }\r
+\r
+ public String getClockDelimeter() {\r
+ try {\r
+ return LocaleService.getClockDelimiter(currentLocale);\r
+ } catch (final LocaleNotLoadedException e) {\r
+ // TODO redirect to console\r
+ System.out.println(e + ":" + e.getMessage());\r
+ }\r
+ return ":";\r
+ }\r
+\r
+ public String[] getAmPmStrings() {\r
+ try {\r
+ return LocaleService.getAmPmStrings(currentLocale);\r
+ } catch (final LocaleNotLoadedException e) {\r
+ // TODO redirect to console\r
+ System.out.println(e + ":" + e.getMessage());\r
+ }\r
+ final String[] temp = new String[2];\r
+ temp[0] = "AM";\r
+ temp[1] = "PM";\r
+ return temp;\r
+ }\r
+\r
+ public int getStartWeekDay(Date date) {\r
+ final Date dateForFirstOfThisMonth = new Date(date.getYear(), date\r
+ .getMonth(), 1);\r
+ int firstDay;\r
+ try {\r
+ firstDay = LocaleService.getFirstDayOfWeek(currentLocale);\r
+ } catch (final LocaleNotLoadedException e) {\r
+ firstDay = 0;\r
+ // TODO redirect to console\r
+ System.out.println(e + ":" + e.getMessage());\r
+ }\r
+ int start = dateForFirstOfThisMonth.getDay() - firstDay;\r
+ if (start < 0) {\r
+ start = 6;\r
+ }\r
+ return start;\r
+ }\r
+\r
+ public static int getNumberOfDaysInMonth(Date date) {\r
+ final int month = date.getMonth();\r
+ if (month == 1 && true == isLeapYear(date)) {\r
+ return 29;\r
+ }\r
+ return maxDaysInMonth[month];\r
+ }\r
+\r
+ public static boolean isLeapYear(Date date) {\r
+ // Instantiate the date for 1st March of that year\r
+ final Date firstMarch = new Date(date.getYear(), 2, 1);\r
+\r
+ // Go back 1 day\r
+ final long firstMarchTime = firstMarch.getTime();\r
+ final long lastDayTimeFeb = firstMarchTime - (24 * 60 * 60 * 1000); // NUM_MILLISECS_A_DAY\r
+\r
+ // Instantiate new Date with this time\r
+ final Date febLastDay = new Date(lastDayTimeFeb);\r
+\r
+ // Check for date in this new instance\r
+ return (29 == febLastDay.getDate()) ? true : false;\r
+ }\r
+\r
+ public static boolean isSameDay(Date d1, Date d2) {\r
+ return (getDayInt(d1) == getDayInt(d2));\r
+ }\r
+\r
+ public static boolean isInRange(Date date, Date rangeStart, Date rangeEnd,\r
+ int resolution) {\r
+ Date s;\r
+ Date e;\r
+ if (rangeStart.after(rangeEnd)) {\r
+ s = rangeEnd;\r
+ e = rangeStart;\r
+ } else {\r
+ e = rangeEnd;\r
+ s = rangeStart;\r
+ }\r
+ long start = s.getYear() * 10000000000l;\r
+ long end = e.getYear() * 10000000000l;\r
+ long target = date.getYear() * 10000000000l;\r
+\r
+ if (resolution == RESOLUTION_YEAR) {\r
+ return (start <= target && end >= target);\r
+ }\r
+ start += s.getMonth() * 100000000;\r
+ end += e.getMonth() * 100000000;\r
+ target += date.getMonth() * 100000000;\r
+ if (resolution == RESOLUTION_MONTH) {\r
+ return (start <= target && end >= target);\r
+ }\r
+ start += s.getDate() * 1000000;\r
+ end += e.getDate() * 1000000;\r
+ target += date.getDate() * 1000000;\r
+ if (resolution == RESOLUTION_DAY) {\r
+ return (start <= target && end >= target);\r
+ }\r
+ start += s.getHours() * 10000;\r
+ end += e.getHours() * 10000;\r
+ target += date.getHours() * 10000;\r
+ if (resolution == RESOLUTION_HOUR) {\r
+ return (start <= target && end >= target);\r
+ }\r
+ start += s.getMinutes() * 100;\r
+ end += e.getMinutes() * 100;\r
+ target += date.getMinutes() * 100;\r
+ if (resolution == RESOLUTION_MIN) {\r
+ return (start <= target && end >= target);\r
+ }\r
+ start += s.getSeconds();\r
+ end += e.getSeconds();\r
+ target += date.getSeconds();\r
+ return (start <= target && end >= target);\r
+\r
+ }\r
+\r
+ private static int getDayInt(Date date) {\r
+ final int y = date.getYear();\r
+ final int m = date.getMonth();\r
+ final int d = date.getDate();\r
+\r
+ return ((y + 1900) * 10000 + m * 100 + d) * 1000000000;\r
+ }\r
+\r
+}\r
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client;
+
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.Panel;
+import com.google.gwt.user.client.ui.ScrollPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.itmill.toolkit.terminal.gwt.client.ui.IWindow;
+
+public final class DebugConsole extends IWindow implements Console {
+
+ private final Panel panel;
+
+ public DebugConsole(ApplicationConnection client) {
+ super();
+ this.client = client;
+ panel = new FlowPanel();
+ final ScrollPanel p = new ScrollPanel();
+ p.add(panel);
+ setWidget(p);
+ setCaption("Debug window");
+ minimize();
+ show();
+ }
+
+ private void minimize() {
+ // TODO stack to bottom (create window manager of some sort)
+ setPixelSize(60, 60);
+ setPopupPosition(Window.getClientWidth()
+ - (100 + IWindow.BORDER_WIDTH_HORIZONTAL), 0);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.itmill.toolkit.terminal.gwt.client.Console#log(java.lang.String)
+ */
+ public void log(String msg) {
+ panel.add(new HTML(msg));
+ System.out.println(msg);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.itmill.toolkit.terminal.gwt.client.Console#error(java.lang.String)
+ */
+ public void error(String msg) {
+ panel.add((new HTML(msg)));
+ System.out.println(msg);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.itmill.toolkit.terminal.gwt.client.Console#printObject(java.lang.Object)
+ */
+ public void printObject(Object msg) {
+ panel.add((new Label(msg.toString())));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.itmill.toolkit.terminal.gwt.client.Console#dirUIDL(com.itmill.toolkit.terminal.gwt.client.UIDL)
+ */
+ public void dirUIDL(UIDL u) {
+ panel.add(u.print_r());
+ }
+
+ public void setSize(Event event, boolean updateVariables) {
+ super.setSize(event, false);
+ }
+
+ public void onScroll(Widget widget, int scrollLeft, int scrollTop) {
+
+ }
+
+ public void setPopupPosition(int left, int top) {
+ // Keep the popup within the browser's client area, so that they can't
+ // get
+ // 'lost' and become impossible to interact with. Note that we don't
+ // attempt
+ // to keep popups pegged to the bottom and right edges, as they will
+ // then
+ // cause scrollbars to appear, so the user can't lose them.
+ if (left < 0) {
+ left = 0;
+ }
+ if (top < 0) {
+ top = 0;
+ }
+
+ // Set the popup's position manually, allowing setPopupPosition() to be
+ // called before show() is called (so a popup can be positioned without
+ // it
+ // 'jumping' on the screen).
+ Element elem = getElement();
+ DOM.setStyleAttribute(elem, "left", left + "px");
+ DOM.setStyleAttribute(elem, "top", top + "px");
+ }
+
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.user.client.ui.Widget;
+import com.itmill.toolkit.terminal.gwt.client.ui.IAccordion;
+import com.itmill.toolkit.terminal.gwt.client.ui.IButton;
+import com.itmill.toolkit.terminal.gwt.client.ui.ICheckBox;
+import com.itmill.toolkit.terminal.gwt.client.ui.ICustomComponent;
+import com.itmill.toolkit.terminal.gwt.client.ui.ICustomLayout;
+import com.itmill.toolkit.terminal.gwt.client.ui.IDateFieldCalendar;
+import com.itmill.toolkit.terminal.gwt.client.ui.IEmbedded;
+import com.itmill.toolkit.terminal.gwt.client.ui.IExpandLayout;
+import com.itmill.toolkit.terminal.gwt.client.ui.IFilterSelect;
+import com.itmill.toolkit.terminal.gwt.client.ui.IForm;
+import com.itmill.toolkit.terminal.gwt.client.ui.IFormLayout;
+import com.itmill.toolkit.terminal.gwt.client.ui.IGridLayout;
+import com.itmill.toolkit.terminal.gwt.client.ui.IHorizontalExpandLayout;
+import com.itmill.toolkit.terminal.gwt.client.ui.ILabel;
+import com.itmill.toolkit.terminal.gwt.client.ui.ILink;
+import com.itmill.toolkit.terminal.gwt.client.ui.IListSelect;
+import com.itmill.toolkit.terminal.gwt.client.ui.IMenuBar;
+import com.itmill.toolkit.terminal.gwt.client.ui.INativeSelect;
+import com.itmill.toolkit.terminal.gwt.client.ui.IOptionGroup;
+import com.itmill.toolkit.terminal.gwt.client.ui.IOrderedLayoutHorizontal;
+import com.itmill.toolkit.terminal.gwt.client.ui.IOrderedLayoutVertical;
+import com.itmill.toolkit.terminal.gwt.client.ui.IPanel;
+import com.itmill.toolkit.terminal.gwt.client.ui.IPasswordField;
+import com.itmill.toolkit.terminal.gwt.client.ui.IPopupCalendar;
+import com.itmill.toolkit.terminal.gwt.client.ui.IProgressIndicator;
+import com.itmill.toolkit.terminal.gwt.client.ui.IScrollTable;
+import com.itmill.toolkit.terminal.gwt.client.ui.ISlider;
+import com.itmill.toolkit.terminal.gwt.client.ui.ISplitPanelHorizontal;
+import com.itmill.toolkit.terminal.gwt.client.ui.ISplitPanelVertical;
+import com.itmill.toolkit.terminal.gwt.client.ui.ITablePaging;
+import com.itmill.toolkit.terminal.gwt.client.ui.ITabsheet;
+import com.itmill.toolkit.terminal.gwt.client.ui.ITextArea;
+import com.itmill.toolkit.terminal.gwt.client.ui.ITextField;
+import com.itmill.toolkit.terminal.gwt.client.ui.ITextualDate;
+import com.itmill.toolkit.terminal.gwt.client.ui.ITree;
+import com.itmill.toolkit.terminal.gwt.client.ui.ITwinColSelect;
+import com.itmill.toolkit.terminal.gwt.client.ui.IUnknownComponent;
+import com.itmill.toolkit.terminal.gwt.client.ui.IUpload;
+import com.itmill.toolkit.terminal.gwt.client.ui.IWindow;
+import com.itmill.toolkit.terminal.gwt.client.ui.absolutegrid.ISizeableGridLayout;
+import com.itmill.toolkit.terminal.gwt.client.ui.absolutegrid.ISizeableOrderedLayout;
+import com.itmill.toolkit.terminal.gwt.client.ui.richtextarea.IRichTextArea;
+
+public class DefaultWidgetSet implements WidgetSet {
+
+ /**
+ * This is the entry point method.
+ */
+ public void onModuleLoad() {
+ ArrayList appIds = new ArrayList();
+ ApplicationConfiguration.loadAppIdListFromDOM(appIds);
+ for (Iterator iterator = appIds.iterator(); iterator.hasNext();) {
+ String appId = (String) iterator.next();
+ ApplicationConfiguration appConf = ApplicationConfiguration
+ .getConfigFromDOM(appId);
+ new ApplicationConnection(this, appConf);
+ }
+ }
+
+ public Widget createWidget(UIDL uidl) {
+
+ final String className = resolveWidgetTypeName(uidl);
+ if ("com.itmill.toolkit.terminal.gwt.client.ui.ICheckBox"
+ .equals(className)) {
+ return new ICheckBox();
+ } else if ("com.itmill.toolkit.terminal.gwt.client.ui.IButton"
+ .equals(className)) {
+ return new IButton();
+ } else if ("com.itmill.toolkit.terminal.gwt.client.ui.IWindow"
+ .equals(className)) {
+ return new IWindow();
+ } else if ("com.itmill.toolkit.terminal.gwt.client.ui.absolutegrid.ISizeableOrderedLayout"
+ .equals(className)) {
+ return new ISizeableOrderedLayout();
+ } else if ("com.itmill.toolkit.terminal.gwt.client.ui.IOrderedLayoutVertical"
+ .equals(className)) {
+ return new IOrderedLayoutVertical();
+ } else if ("com.itmill.toolkit.terminal.gwt.client.ui.IOrderedLayoutHorizontal"
+ .equals(className)) {
+ return new IOrderedLayoutHorizontal();
+ } else if ("com.itmill.toolkit.terminal.gwt.client.ui.ILabel"
+ .equals(className)) {
+ return new ILabel();
+ } else if ("com.itmill.toolkit.terminal.gwt.client.ui.ILink"
+ .equals(className)) {
+ return new ILink();
+ } else if ("com.itmill.toolkit.terminal.gwt.client.ui.absolutegrid.ISizeableGridLayout"
+ .equals(className)) {
+ return new ISizeableGridLayout();
+ } else if ("com.itmill.toolkit.terminal.gwt.client.ui.IGridLayout"
+ .equals(className)) {
+ return new IGridLayout();
+ } else if ("com.itmill.toolkit.terminal.gwt.client.ui.ITree"
+ .equals(className)) {
+ return new ITree();
+ } else if ("com.itmill.toolkit.terminal.gwt.client.ui.IOptionGroup"
+ .equals(className)) {
+ return new IOptionGroup();
+ } else if ("com.itmill.toolkit.terminal.gwt.client.ui.ITwinColSelect"
+ .equals(className)) {
+ return new ITwinColSelect();
+ } else if ("com.itmill.toolkit.terminal.gwt.client.ui.INativeSelect"
+ .equals(className)) {
+ return new INativeSelect();
+ } else if ("com.itmill.toolkit.terminal.gwt.client.ui.IListSelect"
+ .equals(className)) {
+ return new IListSelect();
+ } else if ("com.itmill.toolkit.terminal.gwt.client.ui.IPanel"
+ .equals(className)) {
+ return new IPanel();
+ } else if ("com.itmill.toolkit.terminal.gwt.client.ui.ITabsheet"
+ .equals(className)) {
+ return new ITabsheet();
+ } else if ("com.itmill.toolkit.terminal.gwt.client.ui.IEmbedded"
+ .equals(className)) {
+ return new IEmbedded();
+ } else if ("com.itmill.toolkit.terminal.gwt.client.ui.ICustomLayout"
+ .equals(className)) {
+ return new ICustomLayout();
+ } else if ("com.itmill.toolkit.terminal.gwt.client.ui.ICustomComponent"
+ .equals(className)) {
+ return new ICustomComponent();
+ } else if ("com.itmill.toolkit.terminal.gwt.client.ui.ITextArea"
+ .equals(className)) {
+ return new ITextArea();
+ } else if ("com.itmill.toolkit.terminal.gwt.client.ui.IPasswordField"
+ .equals(className)) {
+ return new IPasswordField();
+ } else if ("com.itmill.toolkit.terminal.gwt.client.ui.ITextField"
+ .equals(className)) {
+ return new ITextField();
+ } else if ("com.itmill.toolkit.terminal.gwt.client.ui.ITablePaging"
+ .equals(className)) {
+ return new ITablePaging();
+ } else if ("com.itmill.toolkit.terminal.gwt.client.ui.IScrollTable"
+ .equals(className)) {
+ return new IScrollTable();
+ } else if ("com.itmill.toolkit.terminal.gwt.client.ui.IDateFieldCalendar"
+ .equals(className)) {
+ return new IDateFieldCalendar();
+ } else if ("com.itmill.toolkit.terminal.gwt.client.ui.ITextualDate"
+ .equals(className)) {
+ return new ITextualDate();
+ } else if ("com.itmill.toolkit.terminal.gwt.client.ui.IPopupCalendar"
+ .equals(className)) {
+ return new IPopupCalendar();
+ } else if ("com.itmill.toolkit.terminal.gwt.client.ui.ISlider"
+ .equals(className)) {
+ return new ISlider();
+ } else if ("com.itmill.toolkit.terminal.gwt.client.ui.IForm"
+ .equals(className)) {
+ return new IForm();
+ } else if ("com.itmill.toolkit.terminal.gwt.client.ui.IFormLayout"
+ .equals(className)) {
+ return new IFormLayout();
+ } else if ("com.itmill.toolkit.terminal.gwt.client.ui.IUpload"
+ .equals(className)) {
+ return new IUpload();
+ } else if ("com.itmill.toolkit.terminal.gwt.client.ui.ISplitPanelHorizontal"
+ .equals(className)) {
+ return new ISplitPanelHorizontal();
+ } else if ("com.itmill.toolkit.terminal.gwt.client.ui.ISplitPanelVertical"
+ .equals(className)) {
+ return new ISplitPanelVertical();
+ } else if ("com.itmill.toolkit.terminal.gwt.client.ui.IFilterSelect"
+ .equals(className)) {
+ return new IFilterSelect();
+ } else if ("com.itmill.toolkit.terminal.gwt.client.ui.IProgressIndicator"
+ .equals(className)) {
+ return new IProgressIndicator();
+ } else if ("com.itmill.toolkit.terminal.gwt.client.ui.IExpandLayout"
+ .equals(className)) {
+ return new IExpandLayout();
+ } else if ("com.itmill.toolkit.terminal.gwt.client.ui.IHorizontalExpandLayout"
+ .equals(className)) {
+ return new IHorizontalExpandLayout();
+ } else if ("com.itmill.toolkit.terminal.gwt.client.ui.richtextarea.IRichTextArea"
+ .equals(className)) {
+ return new IRichTextArea();
+ } else if ("com.itmill.toolkit.terminal.gwt.client.ui.IAccordion"
+ .equals(className)) {
+ return new IAccordion();
+ } else if ("com.itmill.toolkit.terminal.gwt.client.ui.IMenuBar"
+ .equals(className)) {
+ return new IMenuBar();
+ }
+
+ return new IUnknownComponent();
+
+ /*
+ * TODO: Class based impl, use when GWT supports return
+ * (Widget)GWT.create(resolveWidgetClass(uidl));
+ */
+ }
+
+ protected String resolveWidgetTypeName(UIDL uidl) {
+
+ final String tag = uidl.getTag();
+ if ("button".equals(tag)) {
+ if ("switch".equals(uidl.getStringAttribute("type"))) {
+ return "com.itmill.toolkit.terminal.gwt.client.ui.ICheckBox";
+ } else {
+ return "com.itmill.toolkit.terminal.gwt.client.ui.IButton";
+ }
+ } else if ("window".equals(tag)) {
+ return "com.itmill.toolkit.terminal.gwt.client.ui.IWindow";
+ } else if ("orderedlayout".equals(tag)) {
+ if ("horizontal".equals(uidl.getStringAttribute("orientation"))) {
+ if (uidl.hasAttribute("height") && uidl.hasAttribute("width")) {
+ return "com.itmill.toolkit.terminal.gwt.client.ui.absolutegrid.ISizeableOrderedLayout";
+ } else {
+ return "com.itmill.toolkit.terminal.gwt.client.ui.IOrderedLayoutHorizontal";
+ }
+ } else {
+ if (uidl.hasAttribute("height")) {
+ return "com.itmill.toolkit.terminal.gwt.client.ui.absolutegrid.ISizeableOrderedLayout";
+ } else {
+ return "com.itmill.toolkit.terminal.gwt.client.ui.IOrderedLayoutVertical";
+ }
+ }
+ } else if ("label".equals(tag)) {
+ return "com.itmill.toolkit.terminal.gwt.client.ui.ILabel";
+ } else if ("link".equals(tag)) {
+ return "com.itmill.toolkit.terminal.gwt.client.ui.ILink";
+ } else if ("gridlayout".equals(tag)) {
+ if (uidl.hasAttribute("height")) {
+ // height needs to be set to use sizeable grid layout, with
+ // width only or no size at all it fails to render properly.
+ return "com.itmill.toolkit.terminal.gwt.client.ui.absolutegrid.ISizeableGridLayout";
+ } else {
+ // Fall back to GWT FlexTable based implementation.
+ return "com.itmill.toolkit.terminal.gwt.client.ui.IGridLayout";
+ }
+ } else if ("tree".equals(tag)) {
+ return "com.itmill.toolkit.terminal.gwt.client.ui.ITree";
+ } else if ("select".equals(tag)) {
+ if (uidl.hasAttribute("type")) {
+ final String type = uidl.getStringAttribute("type");
+ if (type.equals("twincol")) {
+ return "com.itmill.toolkit.terminal.gwt.client.ui.ITwinColSelect";
+ }
+ if (type.equals("optiongroup")) {
+ return "com.itmill.toolkit.terminal.gwt.client.ui.IOptionGroup";
+ }
+ if (type.equals("native")) {
+ return "com.itmill.toolkit.terminal.gwt.client.ui.INativeSelect";
+ }
+ if (type.equals("list")) {
+ return "com.itmill.toolkit.terminal.gwt.client.ui.IListSelect";
+ }
+ } else {
+ if (uidl.hasAttribute("selectmode")
+ && uidl.getStringAttribute("selectmode")
+ .equals("multi")) {
+ return "com.itmill.toolkit.terminal.gwt.client.ui.IListSelect";
+ } else {
+ return "com.itmill.toolkit.terminal.gwt.client.ui.IFilterSelect";
+ }
+ }
+ } else if ("panel".equals(tag)) {
+ return "com.itmill.toolkit.terminal.gwt.client.ui.IPanel";
+ } else if ("tabsheet".equals(tag)) {
+ return "com.itmill.toolkit.terminal.gwt.client.ui.ITabsheet";
+ } else if ("accordion".equals(tag)) {
+ return "com.itmill.toolkit.terminal.gwt.client.ui.IAccordion";
+ } else if ("embedded".equals(tag)) {
+ return "com.itmill.toolkit.terminal.gwt.client.ui.IEmbedded";
+ } else if ("customlayout".equals(tag)) {
+ return "com.itmill.toolkit.terminal.gwt.client.ui.ICustomLayout";
+ } else if ("customcomponent".equals(tag)) {
+ return "com.itmill.toolkit.terminal.gwt.client.ui.ICustomComponent";
+ } else if ("textfield".equals(tag)) {
+ if (uidl.getBooleanAttribute("richtext")) {
+ return "com.itmill.toolkit.terminal.gwt.client.ui.richtextarea.IRichTextArea";
+ } else if (uidl.hasAttribute("multiline")) {
+ return "com.itmill.toolkit.terminal.gwt.client.ui.ITextArea";
+ } else if (uidl.getBooleanAttribute("secret")) {
+ return "com.itmill.toolkit.terminal.gwt.client.ui.IPasswordField";
+ } else {
+ return "com.itmill.toolkit.terminal.gwt.client.ui.ITextField";
+ }
+ } else if ("table".equals(tag)) {
+ return "com.itmill.toolkit.terminal.gwt.client.ui.IScrollTable";
+ } else if ("pagingtable".equals(tag)) {
+ return "com.itmill.toolkit.terminal.gwt.client.ui.ITablePaging";
+ } else if ("datefield".equals(tag)) {
+ if (uidl.hasAttribute("type")) {
+ if ("inline".equals(uidl.getStringAttribute("type"))) {
+ return "com.itmill.toolkit.terminal.gwt.client.ui.IDateFieldCalendar";
+ } else if ("popup".equals(uidl.getStringAttribute("type"))) {
+ return "com.itmill.toolkit.terminal.gwt.client.ui.IPopupCalendar";
+ }
+ }
+ // popup calendar is the default
+ return "com.itmill.toolkit.terminal.gwt.client.ui.IPopupCalendar";
+ } else if ("slider".equals(tag)) {
+ return "com.itmill.toolkit.terminal.gwt.client.ui.ISlider";
+ } else if ("form".equals(tag)) {
+ return "com.itmill.toolkit.terminal.gwt.client.ui.IForm";
+ } else if ("formlayout".equals(tag)) {
+ return "com.itmill.toolkit.terminal.gwt.client.ui.IFormLayout";
+ } else if ("upload".equals(tag)) {
+ return "com.itmill.toolkit.terminal.gwt.client.ui.IUpload";
+ } else if ("hsplitpanel".equals(tag)) {
+ return "com.itmill.toolkit.terminal.gwt.client.ui.ISplitPanelHorizontal";
+ } else if ("vsplitpanel".equals(tag)) {
+ return "com.itmill.toolkit.terminal.gwt.client.ui.ISplitPanelVertical";
+ } else if ("progressindicator".equals(tag)) {
+ return "com.itmill.toolkit.terminal.gwt.client.ui.IProgressIndicator";
+ } else if ("expandlayout".equals(tag)) {
+ if ("horizontal".equals(uidl.getStringAttribute("orientation"))) {
+ return "com.itmill.toolkit.terminal.gwt.client.ui.IHorizontalExpandLayout";
+ } else {
+ return "com.itmill.toolkit.terminal.gwt.client.ui.IExpandLayout";
+ }
+ } else if ("menubar".equals(tag)) {
+ return "com.itmill.toolkit.terminal.gwt.client.ui.IMenuBar";
+ }
+
+ return "com.itmill.toolkit.terminal.gwt.client.ui.IUnknownComponent";
+
+ /*
+ * TODO: use class based impl when GWT supports it
+ */
+ }
+
+ public boolean isCorrectImplementation(Widget currentWidget, UIDL uidl) {
+ return GWT.getTypeName(currentWidget).equals(
+ resolveWidgetTypeName(uidl));
+ }
+
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client;
+
+import java.util.Iterator;
+
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.HTML;
+import com.itmill.toolkit.terminal.gwt.client.ui.ToolkitOverlay;
+
+public class ErrorMessage extends FlowPanel {
+ public static final String CLASSNAME = "i-errormessage";
+
+ public ErrorMessage() {
+ super();
+ setStyleName(CLASSNAME);
+ }
+
+ public void updateFromUIDL(UIDL uidl) {
+ clear();
+ if (uidl.getChildCount() == 0) {
+ add(new HTML(" "));
+ } else {
+ for (final Iterator it = uidl.getChildIterator(); it.hasNext();) {
+ final Object child = it.next();
+ if (child instanceof String) {
+ final String errorMessage = (String) child;
+ add(new HTML(errorMessage));
+ } else if (child instanceof UIDL.XML) {
+ final UIDL.XML xml = (UIDL.XML) child;
+ add(new HTML(xml.getXMLAsString()));
+ } else {
+ final ErrorMessage childError = new ErrorMessage();
+ add(childError);
+ childError.updateFromUIDL((UIDL) child);
+ }
+ }
+ }
+ }
+
+ /**
+ * Shows this error message next to given element.
+ *
+ * @param indicatorElement
+ */
+ public void showAt(Element indicatorElement) {
+ ToolkitOverlay errorContainer = (ToolkitOverlay) getParent();
+ if (errorContainer == null) {
+ errorContainer = new ToolkitOverlay();
+ errorContainer.setWidget(this);
+ }
+ errorContainer.setPopupPosition(DOM.getAbsoluteLeft(indicatorElement)
+ + 2
+ * DOM.getElementPropertyInt(indicatorElement, "offsetHeight"),
+ DOM.getAbsoluteTop(indicatorElement)
+ + 2
+ * DOM.getElementPropertyInt(indicatorElement,
+ "offsetHeight"));
+ errorContainer.show();
+
+ }
+
+ public void hide() {
+ final ToolkitOverlay errorContainer = (ToolkitOverlay) getParent();
+ if (errorContainer != null) {
+ errorContainer.hide();
+ }
+ }
+}
--- /dev/null
+package com.itmill.toolkit.terminal.gwt.client;
+
+/**
+ * GWT's HasFocus is way too overkill for just receiving focus in simple
+ * components. Toolkit uses this interface in addition to GWT's HasFocus to pass
+ * focus requests from server to actual ui widgets in browsers.
+ *
+ * So in to make your server side focusable component receive focus on client
+ * side it must either implement this or HasFocus interface.
+ */
+public interface Focusable {
+ /**
+ * Sets focus to this widget.
+ */
+ public void focus();
+}
--- /dev/null
+/* \r
+@ITMillApache2LicenseForJavaFiles@\r
+ */\r
+\r
+package com.itmill.toolkit.terminal.gwt.client;\r
+\r
+public class LocaleNotLoadedException extends Exception {\r
+\r
+ /**\r
+ * Serial generated by Eclipse.\r
+ */\r
+ private static final long serialVersionUID = 2005227056545210838L;\r
+\r
+ public LocaleNotLoadedException(String locale) {\r
+ super(locale);\r
+ }\r
+}\r
--- /dev/null
+/* \r
+@ITMillApache2LicenseForJavaFiles@\r
+ */\r
+\r
+package com.itmill.toolkit.terminal.gwt.client;\r
+\r
+import java.util.HashMap;\r
+import java.util.Map;\r
+import java.util.Set;\r
+\r
+import com.google.gwt.json.client.JSONArray;\r
+import com.google.gwt.json.client.JSONBoolean;\r
+import com.google.gwt.json.client.JSONNumber;\r
+import com.google.gwt.json.client.JSONObject;\r
+import com.google.gwt.json.client.JSONString;\r
+\r
+/**\r
+ * Date / time etc. localisation service for all widgets. Caches all loaded\r
+ * locales as JSONObjects.\r
+ * \r
+ * @author IT Mill Ltd.\r
+ * \r
+ */\r
+public class LocaleService {\r
+\r
+ private static Map cache = new HashMap();\r
+ private static String defaultLocale;\r
+\r
+ public static void addLocale(JSONObject json) {\r
+ final String key = ((JSONString) json.get("name")).stringValue();\r
+ if (cache.containsKey(key)) {\r
+ cache.remove(key);\r
+ }\r
+ cache.put(key, json);\r
+ if (cache.size() == 1) {\r
+ setDefaultLocale(key);\r
+ }\r
+ }\r
+\r
+ public static void setDefaultLocale(String locale) {\r
+ defaultLocale = locale;\r
+ }\r
+\r
+ public static String getDefaultLocale() {\r
+ return defaultLocale;\r
+ }\r
+\r
+ public static Set getAvailableLocales() {\r
+ return cache.keySet();\r
+ }\r
+\r
+ public static String[] getMonthNames(String locale)\r
+ throws LocaleNotLoadedException {\r
+ if (cache.containsKey(locale)) {\r
+ final JSONObject l = (JSONObject) cache.get(locale);\r
+ final JSONArray mn = (JSONArray) l.get("mn");\r
+ final String[] temp = new String[12];\r
+ temp[0] = ((JSONString) mn.get(0)).stringValue();\r
+ temp[1] = ((JSONString) mn.get(1)).stringValue();\r
+ temp[2] = ((JSONString) mn.get(2)).stringValue();\r
+ temp[3] = ((JSONString) mn.get(3)).stringValue();\r
+ temp[4] = ((JSONString) mn.get(4)).stringValue();\r
+ temp[5] = ((JSONString) mn.get(5)).stringValue();\r
+ temp[6] = ((JSONString) mn.get(6)).stringValue();\r
+ temp[7] = ((JSONString) mn.get(7)).stringValue();\r
+ temp[8] = ((JSONString) mn.get(8)).stringValue();\r
+ temp[9] = ((JSONString) mn.get(9)).stringValue();\r
+ temp[10] = ((JSONString) mn.get(10)).stringValue();\r
+ temp[11] = ((JSONString) mn.get(11)).stringValue();\r
+ return temp;\r
+ } else {\r
+ throw new LocaleNotLoadedException(locale);\r
+ }\r
+ }\r
+\r
+ public static String[] getShortMonthNames(String locale)\r
+ throws LocaleNotLoadedException {\r
+ if (cache.containsKey(locale)) {\r
+ final JSONObject l = (JSONObject) cache.get(locale);\r
+ final JSONArray smn = (JSONArray) l.get("smn");\r
+ final String[] temp = new String[12];\r
+ temp[0] = ((JSONString) smn.get(0)).stringValue();\r
+ temp[1] = ((JSONString) smn.get(1)).stringValue();\r
+ temp[2] = ((JSONString) smn.get(2)).stringValue();\r
+ temp[3] = ((JSONString) smn.get(3)).stringValue();\r
+ temp[4] = ((JSONString) smn.get(4)).stringValue();\r
+ temp[5] = ((JSONString) smn.get(5)).stringValue();\r
+ temp[6] = ((JSONString) smn.get(6)).stringValue();\r
+ temp[7] = ((JSONString) smn.get(7)).stringValue();\r
+ temp[8] = ((JSONString) smn.get(8)).stringValue();\r
+ temp[9] = ((JSONString) smn.get(9)).stringValue();\r
+ temp[10] = ((JSONString) smn.get(10)).stringValue();\r
+ temp[11] = ((JSONString) smn.get(11)).stringValue();\r
+ return temp;\r
+ } else {\r
+ throw new LocaleNotLoadedException(locale);\r
+ }\r
+ }\r
+\r
+ public static String[] getDayNames(String locale)\r
+ throws LocaleNotLoadedException {\r
+ if (cache.containsKey(locale)) {\r
+ final JSONObject l = (JSONObject) cache.get(locale);\r
+ final JSONArray dn = (JSONArray) l.get("dn");\r
+ final String[] temp = new String[7];\r
+ temp[0] = ((JSONString) dn.get(0)).stringValue();\r
+ temp[1] = ((JSONString) dn.get(1)).stringValue();\r
+ temp[2] = ((JSONString) dn.get(2)).stringValue();\r
+ temp[3] = ((JSONString) dn.get(3)).stringValue();\r
+ temp[4] = ((JSONString) dn.get(4)).stringValue();\r
+ temp[5] = ((JSONString) dn.get(5)).stringValue();\r
+ temp[6] = ((JSONString) dn.get(6)).stringValue();\r
+ return temp;\r
+ } else {\r
+ throw new LocaleNotLoadedException(locale);\r
+ }\r
+ }\r
+\r
+ public static String[] getShortDayNames(String locale)\r
+ throws LocaleNotLoadedException {\r
+ if (cache.containsKey(locale)) {\r
+ final JSONObject l = (JSONObject) cache.get(locale);\r
+ final JSONArray sdn = (JSONArray) l.get("sdn");\r
+ final String[] temp = new String[7];\r
+ temp[0] = ((JSONString) sdn.get(0)).stringValue();\r
+ temp[1] = ((JSONString) sdn.get(1)).stringValue();\r
+ temp[2] = ((JSONString) sdn.get(2)).stringValue();\r
+ temp[3] = ((JSONString) sdn.get(3)).stringValue();\r
+ temp[4] = ((JSONString) sdn.get(4)).stringValue();\r
+ temp[5] = ((JSONString) sdn.get(5)).stringValue();\r
+ temp[6] = ((JSONString) sdn.get(6)).stringValue();\r
+ return temp;\r
+ } else {\r
+ throw new LocaleNotLoadedException(locale);\r
+ }\r
+ }\r
+\r
+ public static int getFirstDayOfWeek(String locale)\r
+ throws LocaleNotLoadedException {\r
+ if (cache.containsKey(locale)) {\r
+ final JSONObject l = (JSONObject) cache.get(locale);\r
+ final JSONNumber fdow = (JSONNumber) l.get("fdow");\r
+ return (int) fdow.getValue();\r
+ } else {\r
+ throw new LocaleNotLoadedException(locale);\r
+ }\r
+ }\r
+\r
+ public static String getDateFormat(String locale)\r
+ throws LocaleNotLoadedException {\r
+ if (cache.containsKey(locale)) {\r
+ final JSONObject l = (JSONObject) cache.get(locale);\r
+ final JSONString df = (JSONString) l.get("df");\r
+ return df.stringValue();\r
+ } else {\r
+ throw new LocaleNotLoadedException(locale);\r
+ }\r
+ }\r
+\r
+ public static boolean isTwelveHourClock(String locale)\r
+ throws LocaleNotLoadedException {\r
+ if (cache.containsKey(locale)) {\r
+ final JSONObject l = (JSONObject) cache.get(locale);\r
+ final JSONBoolean thc = (JSONBoolean) l.get("thc");\r
+ return thc.booleanValue();\r
+ } else {\r
+ throw new LocaleNotLoadedException(locale);\r
+ }\r
+ }\r
+\r
+ public static String getClockDelimiter(String locale)\r
+ throws LocaleNotLoadedException {\r
+ if (cache.containsKey(locale)) {\r
+ final JSONObject l = (JSONObject) cache.get(locale);\r
+ final JSONString hmd = (JSONString) l.get("hmd");\r
+ return hmd.stringValue();\r
+ } else {\r
+ throw new LocaleNotLoadedException(locale);\r
+ }\r
+ }\r
+\r
+ public static String[] getAmPmStrings(String locale)\r
+ throws LocaleNotLoadedException {\r
+ if (cache.containsKey(locale)) {\r
+ final JSONObject l = (JSONObject) cache.get(locale);\r
+ final JSONArray ampm = (JSONArray) l.get("ampm");\r
+ final String[] temp = new String[2];\r
+ temp[0] = ((JSONString) ampm.get(0)).stringValue();\r
+ temp[1] = ((JSONString) ampm.get(1)).stringValue();\r
+ return temp;\r
+ } else {\r
+ throw new LocaleNotLoadedException(locale);\r
+ }\r
+\r
+ }\r
+\r
+}\r
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client;
+
+/**
+ * Client side console implementation for non-debug mode that discards all
+ * messages.
+ *
+ */
+public class NullConsole implements Console {
+
+ public void dirUIDL(UIDL u) {
+ }
+
+ public void error(String msg) {
+ }
+
+ public void log(String msg) {
+ }
+
+ public void printObject(Object msg) {
+ }
+
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client;
+
+public interface Paintable {
+
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client);
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client;
+
+public class StyleConstants {
+
+ public static final String MARGIN_TOP = "margin-top";
+ public static final String MARGIN_RIGHT = "margin-right";
+ public static final String MARGIN_BOTTOM = "margin-bottom";
+ public static final String MARGIN_LEFT = "margin-left";
+
+ public static final String VERTICAL_SPACING = "vspacing";
+ public static final String HORIZONTAL_SPACING = "hspacing";
+
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+package com.itmill.toolkit.terminal.gwt.client;
+
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.itmill.toolkit.terminal.gwt.client.ui.ToolkitOverlay;
+
+/**
+ * TODO open for extension
+ */
+public class Tooltip extends ToolkitOverlay {
+ private static final String CLASSNAME = "i-tooltip";
+ private static final int MARGIN = 4;
+ public static final int TOOLTIP_EVENTS = Event.ONKEYDOWN
+ | Event.ONMOUSEOVER | Event.ONMOUSEOUT | Event.ONMOUSEMOVE
+ | Event.ONCLICK;
+ protected static final int MAX_WIDTH = 500;
+ ErrorMessage em = new ErrorMessage();
+ Element description = DOM.createDiv();
+ private Paintable tooltipOwner;
+ private boolean closing = false;
+ private boolean opening = false;
+ private ApplicationConnection ac;
+
+ public Tooltip(ApplicationConnection client) {
+ ac = client;
+ setStyleName(CLASSNAME);
+ FlowPanel layout = new FlowPanel();
+ setWidget(layout);
+ layout.add(em);
+ DOM.setElementProperty(description, "className", CLASSNAME + "-text");
+ DOM.appendChild(layout.getElement(), description);
+ }
+
+ private void show(TooltipInfo info) {
+ boolean hasContent = false;
+ if (info.getErrorUidl() != null) {
+ em.setVisible(true);
+ em.updateFromUIDL(info.getErrorUidl());
+ hasContent = true;
+ } else {
+ em.setVisible(false);
+ }
+ if (info.getTitle() != null && !"".equals(info.getTitle())) {
+ DOM.setInnerHTML(description, info.getTitle());
+ DOM.setStyleAttribute(description, "display", "");
+ hasContent = true;
+ } else {
+ DOM.setInnerHTML(description, "");
+ DOM.setStyleAttribute(description, "display", "none");
+ }
+ if (hasContent) {
+ setPopupPositionAndShow(new PositionCallback() {
+ public void setPosition(int offsetWidth, int offsetHeight) {
+
+ if (offsetWidth > MAX_WIDTH) {
+ setWidth(MAX_WIDTH + "px");
+ }
+
+ offsetWidth = getOffsetWidth();
+
+ int x = tooltipEventMouseX + 10 + Window.getScrollLeft();
+ int y = tooltipEventMouseY + 10 + Window.getScrollTop();
+
+ if (x + offsetWidth + MARGIN - Window.getScrollLeft() > Window
+ .getClientWidth()) {
+ x = Window.getClientWidth() - offsetWidth - MARGIN;
+ }
+
+ if (y + offsetHeight + MARGIN - Window.getScrollTop() > Window
+ .getClientHeight()) {
+ y = tooltipEventMouseY - 5 - offsetHeight;
+ }
+
+ setPopupPosition(x, y);
+ sinkEvents(Event.ONMOUSEOVER | Event.ONMOUSEOUT);
+ }
+ });
+ } else {
+ hide();
+ }
+ }
+
+ public void showTooltip(Paintable owner, Event event) {
+ if (closing && tooltipOwner == owner) {
+ closeTimer.cancel();
+ closing = false;
+ return;
+ }
+ updatePosition(event);
+
+ if (opening) {
+ showTimer.cancel();
+ }
+ tooltipOwner = owner;
+ showTimer.schedule(1000);
+ opening = true;
+
+ }
+
+ private Timer showTimer = new Timer() {
+ public void run() {
+ TooltipInfo info = ac.getTitleInfo(tooltipOwner);
+ show(info);
+ opening = false;
+
+ }
+ };
+
+ private Timer closeTimer = new Timer() {
+ public void run() {
+ hide();
+ closing = false;
+ tooltipOwner = null;
+ setWidth("");
+ }
+ };
+
+ public void hideTooltip() {
+ if (opening) {
+ showTimer.cancel();
+ opening = false;
+ tooltipOwner = null;
+ }
+ if (!isAttached()) {
+ return;
+ }
+ if (closing) {
+ // already about to close
+ return;
+ }
+ closeTimer.schedule(300);
+ closing = true;
+ }
+
+ private int tooltipEventMouseX;
+ private int tooltipEventMouseY;
+
+ public void updatePosition(Event event) {
+ tooltipEventMouseX = DOM.eventGetClientX(event);
+ tooltipEventMouseY = DOM.eventGetClientY(event);
+
+ }
+
+ public void handleTooltipEvent(Event event, Paintable owner) {
+ final int type = DOM.eventGetType(event);
+ if ((Tooltip.TOOLTIP_EVENTS & type) == type) {
+ if (type == Event.ONMOUSEOVER) {
+ showTooltip(owner, event);
+ } else if (type == Event.ONMOUSEMOVE) {
+ updatePosition(event);
+ } else {
+ hideTooltip();
+ }
+ } else {
+ // non-tooltip event, hide tooltip
+ hideTooltip();
+ }
+ }
+
+ public void onBrowserEvent(Event event) {
+ final int type = DOM.eventGetType(event);
+ // cancel closing event if tooltip is mouseovered; the user might want
+ // to scroll of cut&paste
+
+ switch (type) {
+ case Event.ONMOUSEOVER:
+ closeTimer.cancel();
+ closing = false;
+ break;
+ case Event.ONMOUSEOUT:
+ hideTooltip();
+ break;
+ default:
+ // NOP
+ }
+ }
+
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+package com.itmill.toolkit.terminal.gwt.client;
+
+public class TooltipInfo {
+
+ private String title;
+
+ private UIDL errorUidl;
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public UIDL getErrorUidl() {
+ return errorUidl;
+ }
+
+ public void setErrorUidl(UIDL errorUidl) {
+ this.errorUidl = errorUidl;
+ }
+
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import com.google.gwt.json.client.JSONArray;
+import com.google.gwt.json.client.JSONBoolean;
+import com.google.gwt.json.client.JSONNumber;
+import com.google.gwt.json.client.JSONObject;
+import com.google.gwt.json.client.JSONString;
+import com.google.gwt.json.client.JSONValue;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.ui.Tree;
+import com.google.gwt.user.client.ui.TreeItem;
+import com.google.gwt.user.client.ui.TreeListener;
+
+public class UIDL {
+
+ JSONArray json;
+
+ public UIDL(JSONArray json) {
+ this.json = json;
+ }
+
+ public String getId() {
+ final JSONValue val = ((JSONObject) json.get(1)).get("id");
+ if (val == null) {
+ return null;
+ }
+ return ((JSONString) val).stringValue();
+ }
+
+ public String getTag() {
+ return ((JSONString) json.get(0)).stringValue();
+ }
+
+ public String getStringAttribute(String name) {
+ final JSONValue val = ((JSONObject) json.get(1)).get(name);
+ if (val == null) {
+ return null;
+ }
+ return ((JSONString) val).stringValue();
+ }
+
+ public Set getAttributeNames() {
+ final HashSet attrs = new HashSet(((JSONObject) json.get(1)).keySet());
+ attrs.remove("v");
+ return attrs;
+ }
+
+ public int getIntAttribute(String name) {
+ final JSONValue val = ((JSONObject) json.get(1)).get(name);
+ if (val == null) {
+ return 0;
+ }
+ final double num = ((JSONNumber) val).getValue();
+ return (int) num;
+ }
+
+ public long getLongAttribute(String name) {
+ final JSONValue val = ((JSONObject) json.get(1)).get(name);
+ if (val == null) {
+ return 0;
+ }
+ final double num = ((JSONNumber) val).getValue();
+ return (long) num;
+ }
+
+ public float getFloatAttribute(String name) {
+ final JSONValue val = ((JSONObject) json.get(1)).get(name);
+ if (val == null) {
+ return 0;
+ }
+ final double num = ((JSONNumber) val).getValue();
+ return (float) num;
+ }
+
+ public double getDoubleAttribute(String name) {
+ final JSONValue val = ((JSONObject) json.get(1)).get(name);
+ if (val == null) {
+ return 0;
+ }
+ final double num = ((JSONNumber) val).getValue();
+ return num;
+ }
+
+ public boolean getBooleanAttribute(String name) {
+ final JSONValue val = ((JSONObject) json.get(1)).get(name);
+ if (val == null) {
+ return false;
+ }
+ return ((JSONBoolean) val).booleanValue();
+ }
+
+ public String[] getStringArrayAttribute(String name) {
+ final JSONArray a = (JSONArray) ((JSONObject) json.get(1)).get(name);
+ final String[] s = new String[a.size()];
+ for (int i = 0; i < a.size(); i++) {
+ s[i] = ((JSONString) a.get(i)).stringValue();
+ }
+ return s;
+ }
+
+ public int[] getIntArrayAttribute(String name) {
+ final JSONArray a = (JSONArray) ((JSONObject) json.get(1)).get(name);
+ final int[] s = new int[a.size()];
+ for (int i = 0; i < a.size(); i++) {
+ s[i] = Integer.parseInt(((JSONString) a.get(i)).stringValue());
+ }
+ return s;
+ }
+
+ public HashSet getStringArrayAttributeAsSet(String string) {
+ final JSONArray a = getArrayVariable(string);
+ final HashSet s = new HashSet();
+ for (int i = 0; i < a.size(); i++) {
+ s.add(((JSONString) a.get(i)).stringValue());
+ }
+ return s;
+ }
+
+ /**
+ * Get attributes value as string whateever the type is
+ *
+ * @param name
+ * @return string presentation of attribute
+ */
+ private String getAttribute(String name) {
+ return json.get(1).isObject().get(name).toString();
+ }
+
+ public boolean hasAttribute(String name) {
+ return ((JSONObject) json.get(1)).get(name) != null;
+ }
+
+ public UIDL getChildUIDL(int i) {
+
+ final JSONValue c = json.get(i + 2);
+ if (c == null) {
+ return null;
+ }
+ if (c.isArray() != null) {
+ return new UIDL(c.isArray());
+ }
+ throw new IllegalStateException("Child node " + i
+ + " is not of type UIDL");
+ }
+
+ public String getChildString(int i) {
+
+ final JSONValue c = json.get(i + 2);
+ if (c.isString() != null) {
+ return ((JSONString) c).stringValue();
+ }
+ throw new IllegalStateException("Child node " + i
+ + " is not of type String");
+ }
+
+ public Iterator getChildIterator() {
+
+ return new Iterator() {
+
+ int index = 2;
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ public Object next() {
+
+ if (json.size() > index) {
+ final JSONValue c = json.get(index++);
+ if (c.isString() != null) {
+ return c.isString().stringValue();
+ } else if (c.isArray() != null) {
+ return new UIDL(c.isArray());
+ } else if (c.isObject() != null) {
+ return new XML(c.isObject());
+ } else {
+ throw new IllegalStateException("Illegal child " + c
+ + " in tag " + getTag() + " at index " + index);
+ }
+ }
+ return null;
+ }
+
+ public boolean hasNext() {
+ return json.size() > index;
+ }
+
+ };
+ }
+
+ public int getNumberOfChildren() {
+ return json.size() - 2;
+ }
+
+ public String toString() {
+ String s = "<" + getTag();
+
+ for (final Iterator i = getAttributeNames().iterator(); i.hasNext();) {
+ final String name = i.next().toString();
+ s += " " + name + "=";
+ final JSONValue v = ((JSONObject) json.get(1)).get(name);
+ if (v.isString() != null) {
+ s += v;
+ } else {
+ s += "\"" + v + "\"";
+ }
+ }
+
+ s += ">\n";
+
+ final Iterator i = getChildIterator();
+ while (i.hasNext()) {
+ final Object c = i.next();
+ s += c.toString();
+ }
+
+ s += "</" + getTag() + ">\n";
+
+ return s;
+ }
+
+ public String getChildrenAsXML() {
+ String s = "";
+ final Iterator i = getChildIterator();
+ while (i.hasNext()) {
+ final Object c = i.next();
+ s += c.toString();
+ }
+ return s;
+ }
+
+ public UIDLBrowser print_r() {
+ return new UIDLBrowser();
+ }
+
+ private class UIDLBrowser extends Tree {
+ public UIDLBrowser() {
+
+ DOM.setStyleAttribute(getElement(), "position", "");
+
+ final TreeItem root = new TreeItem(getTag());
+ addItem(root);
+ root.addItem("");
+ addTreeListener(new TreeListener() {
+
+ public void onTreeItemStateChanged(TreeItem item) {
+ if (item == root) {
+ removeItem(root);
+ UIDLBrowser.this.removeTreeListener(this);
+ addItem(dir());
+ final Iterator it = treeItemIterator();
+ while (it.hasNext()) {
+ ((TreeItem) it.next()).setState(true);
+ }
+ }
+ }
+
+ public void onTreeItemSelected(TreeItem item) {
+ }
+
+ });
+
+ }
+
+ protected boolean isKeyboardNavigationEnabled(TreeItem currentItem) {
+ return false;
+ }
+
+ }
+
+ public TreeItem dir() {
+
+ String nodeName = getTag();
+ for (final Iterator i = getAttributeNames().iterator(); i.hasNext();) {
+ final String name = i.next().toString();
+ final String value = getAttribute(name);
+ nodeName += " " + name + "=" + value;
+ }
+ final TreeItem item = new TreeItem(nodeName);
+
+ try {
+ TreeItem tmp = null;
+ for (final Iterator i = getVariableHash().keySet().iterator(); i
+ .hasNext();) {
+ final String name = i.next().toString();
+ String value = "";
+ try {
+ value = getStringVariable(name);
+ } catch (final Exception e) {
+ try {
+ final JSONArray a = getArrayVariable(name);
+ value = a.toString();
+ } catch (final Exception e2) {
+ try {
+ final int intVal = getIntVariable(name);
+ value = String.valueOf(intVal);
+ } catch (final Exception e3) {
+ value = "unknown";
+ }
+ }
+ }
+ if (tmp == null) {
+ tmp = new TreeItem("variables");
+ }
+ tmp.addItem(name + "=" + value);
+ }
+ if (tmp != null) {
+ item.addItem(tmp);
+ }
+ } catch (final Exception e) {
+ // Ingonered, no variables
+ }
+
+ final Iterator i = getChildIterator();
+ while (i.hasNext()) {
+ final Object child = i.next();
+ try {
+ final UIDL c = (UIDL) child;
+ item.addItem(c.dir());
+
+ } catch (final Exception e) {
+ item.addItem(child.toString());
+ }
+ }
+ return item;
+ }
+
+ private JSONObject getVariableHash() {
+ final JSONObject v = (JSONObject) ((JSONObject) json.get(1)).get("v");
+ if (v == null) {
+ throw new IllegalArgumentException("No variables defined in tag.");
+ }
+ return v;
+ }
+
+ public boolean hasVariable(String name) {
+ Object v = null;
+ try {
+ v = getVariableHash().get(name);
+ } catch (final IllegalArgumentException e) {
+ }
+ return v != null;
+ }
+
+ public String getStringVariable(String name) {
+ final JSONString t = (JSONString) getVariableHash().get(name);
+ if (t == null) {
+ throw new IllegalArgumentException("No such variable: " + name);
+ }
+ return t.stringValue();
+ }
+
+ public int getIntVariable(String name) {
+ final JSONNumber t = (JSONNumber) getVariableHash().get(name);
+ if (t == null) {
+ throw new IllegalArgumentException("No such variable: " + name);
+ }
+ return (int) t.getValue();
+ }
+
+ public long getLongVariable(String name) {
+ final JSONNumber t = (JSONNumber) getVariableHash().get(name);
+ if (t == null) {
+ throw new IllegalArgumentException("No such variable: " + name);
+ }
+ return (long) t.getValue();
+ }
+
+ public float getFloatVariable(String name) {
+ final JSONNumber t = (JSONNumber) getVariableHash().get(name);
+ if (t == null) {
+ throw new IllegalArgumentException("No such variable: " + name);
+ }
+ return (float) t.getValue();
+ }
+
+ public double getDoubleVariable(String name) {
+ final JSONNumber t = (JSONNumber) getVariableHash().get(name);
+ if (t == null) {
+ throw new IllegalArgumentException("No such variable: " + name);
+ }
+ return t.getValue();
+ }
+
+ public boolean getBooleanVariable(String name) {
+ final JSONBoolean t = (JSONBoolean) getVariableHash().get(name);
+ if (t == null) {
+ throw new IllegalArgumentException("No such variable: " + name);
+ }
+ return t.booleanValue();
+ }
+
+ private JSONArray getArrayVariable(String name) {
+ final JSONArray t = (JSONArray) getVariableHash().get(name);
+ if (t == null) {
+ throw new IllegalArgumentException("No such variable: " + name);
+ }
+ return t;
+ }
+
+ public String[] getStringArrayVariable(String name) {
+ final JSONArray a = getArrayVariable(name);
+ final String[] s = new String[a.size()];
+ for (int i = 0; i < a.size(); i++) {
+ s[i] = ((JSONString) a.get(i)).stringValue();
+ }
+ return s;
+ }
+
+ public Set getStringArrayVariableAsSet(String name) {
+ final JSONArray a = getArrayVariable(name);
+ final HashSet s = new HashSet();
+ for (int i = 0; i < a.size(); i++) {
+ s.add(((JSONString) a.get(i)).stringValue());
+ }
+ return s;
+ }
+
+ public int[] getIntArrayVariable(String name) {
+ final JSONArray a = getArrayVariable(name);
+ final int[] s = new int[a.size()];
+ for (int i = 0; i < a.size(); i++) {
+ final JSONValue v = a.get(i);
+ s[i] = v.isNumber() != null ? (int) ((JSONNumber) v).getValue()
+ : Integer.parseInt(v.toString());
+ }
+ return s;
+ }
+
+ public class XML {
+ JSONObject x;
+
+ private XML(JSONObject x) {
+ this.x = x;
+ }
+
+ public String getXMLAsString() {
+ final StringBuffer sb = new StringBuffer();
+ for (final Iterator it = x.keySet().iterator(); it.hasNext();) {
+ final String tag = (String) it.next();
+ sb.append("<");
+ sb.append(tag);
+ sb.append(">");
+ sb.append(x.get(tag).isString().stringValue());
+ sb.append("</");
+ sb.append(tag);
+ sb.append(">");
+ }
+ return sb.toString();
+ }
+ }
+
+ public int getChildCount() {
+ return json.size() - 2;
+ }
+
+ public UIDL getErrors() {
+ final JSONArray a = (JSONArray) ((JSONObject) json.get(1)).get("error");
+ return new UIDL(a);
+ }
+
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client;
+
+import java.util.Iterator;
+
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.ui.HasWidgets;
+import com.google.gwt.user.client.ui.Widget;
+
+public class Util {
+
+ /**
+ * Helper method for debugging purposes.
+ *
+ * Stops execution on firefox browsers on a breakpoint.
+ *
+ */
+ public static native void browserDebugger()
+ /*-{
+ if(window.console)
+ debugger;
+ }-*/;
+
+ /**
+ * Nulls oncontextmenu function on given element. We need to manually clear
+ * context menu events due bad browsers memory leaks, since GWT don't
+ * support them.
+ *
+ * @param el
+ */
+ public native static void removeContextMenuEvent(Element el)
+ /*-{
+ el.oncontextmenu = null;
+ }-*/;
+
+ /**
+ * Traverses recursively ancestors until ContainerResizedListener child
+ * widget is found. They will delegate it futher if needed.
+ *
+ * @param container
+ */
+ public static void runDescendentsLayout(HasWidgets container) {
+ final Iterator childWidgets = container.iterator();
+ while (childWidgets.hasNext()) {
+ final Widget child = (Widget) childWidgets.next();
+ if (child instanceof ContainerResizedListener) {
+ ((ContainerResizedListener) child).iLayout();
+ } else if (child instanceof HasWidgets) {
+ final HasWidgets childContainer = (HasWidgets) child;
+ runDescendentsLayout(childContainer);
+ }
+ }
+ }
+
+ /**
+ * Returns closest parent Widget in hierarchy that implements Container
+ * interface
+ *
+ * @param component
+ * @return closest parent Container
+ */
+ public static Container getParentLayout(Widget component) {
+ Widget parent = component.getParent();
+ while (parent != null && !(parent instanceof Container)) {
+ parent = parent.getParent();
+ }
+ if (parent != null && ((Container) parent).hasChildComponent(component)) {
+ return (Container) parent;
+ }
+ return null;
+ }
+
+ /**
+ * Detects if current browser is IE.
+ *
+ * @deprecated use BrowserInfo class instead
+ *
+ * @return true if IE
+ */
+ public static boolean isIE() {
+ return BrowserInfo.get().isIE();
+ }
+
+ /**
+ * Detects if current browser is IE6.
+ *
+ * @deprecated use BrowserInfo class instead
+ *
+ * @return true if IE6
+ */
+ public static boolean isIE6() {
+ return BrowserInfo.get().isIE6();
+ }
+
+ /**
+ * @deprecated use BrowserInfo class instead
+ * @return
+ */
+ public static boolean isIE7() {
+ return BrowserInfo.get().isIE7();
+ }
+
+ /**
+ * @deprecated use BrowserInfo class instead
+ * @return
+ */
+ public static boolean isFF2() {
+ return BrowserInfo.get().isFF2();
+ }
+
+ private static final Element escapeHtmlHelper = DOM.createDiv();
+
+ /**
+ * Converts html entities to text.
+ *
+ * @param html
+ * @return escaped string presentation of given html
+ */
+ public static String escapeHTML(String html) {
+ DOM.setInnerText(escapeHtmlHelper, html);
+ return DOM.getInnerHTML(escapeHtmlHelper);
+ }
+
+ /**
+ * Adds transparent PNG fix to image element; only use for IE6.
+ *
+ * @param el
+ * IMG element
+ * @param blankImageUrl
+ * URL to transparent one-pixel gif
+ */
+ public native static void addPngFix(Element el, String blankImageUrl)
+ /*-{
+ el.attachEvent("onload", function() {
+ var src = el.src;
+ if (src.indexOf(".png")<1) return;
+ var w = el.width||16;
+ var h = el.height||16;
+ el.src =blankImageUrl;
+ el.style.height = h+"px";
+ el.style.width = w+"px";
+ el.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='"+src+"', sizingMethod='crop');";
+ },false);
+ }-*/;
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client;
+
+import com.google.gwt.core.client.EntryPoint;
+import com.google.gwt.user.client.ui.Widget;
+
+public interface WidgetSet extends EntryPoint {
+
+ /**
+ * Create an uninitialized component that best matches given UIDL.
+ *
+ * @param uidl
+ * UIDL to be painted with returned component.
+ * @return New uninitialized and unregistered component that can paint given
+ * UIDL.
+ */
+ public Widget createWidget(UIDL uidl);
+
+ /**
+ * Test if the given component implementation conforms to UIDL.
+ *
+ * @param currentWidget
+ * Current implementation of the component
+ * @param uidl
+ * UIDL to test against
+ * @return true iff createWidget would return a new component of the same
+ * class than currentWidget
+ */
+ public boolean isCorrectImplementation(Widget currentWidget, UIDL uidl);
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client.ui;
+
+import com.google.gwt.user.client.Command;
+
+/**
+ *
+ */
+public abstract class Action implements Command {
+
+ protected ActionOwner owner;
+
+ protected String iconUrl = null;
+
+ protected String caption = "";
+
+ public Action(ActionOwner owner) {
+ this.owner = owner;
+ }
+
+ /**
+ * Executed when action fired
+ */
+ public abstract void execute();
+
+ public String getHTML() {
+ final StringBuffer sb = new StringBuffer();
+ sb.append("<div>");
+ if (getIconUrl() != null) {
+ sb.append("<img src=\"" + getIconUrl() + "\" alt=\"icon\" />");
+ }
+ sb.append(getCaption());
+ sb.append("</div>");
+ return sb.toString();
+ }
+
+ public String getCaption() {
+ return caption;
+ }
+
+ public void setCaption(String caption) {
+ this.caption = caption;
+ }
+
+ public String getIconUrl() {
+ return iconUrl;
+ }
+
+ public void setIconUrl(String url) {
+ iconUrl = url;
+ }
+}
--- /dev/null
+package com.itmill.toolkit.terminal.gwt.client.ui;
+
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;
+
+public interface ActionOwner {
+
+ /**
+ * @return Array of IActions
+ */
+ public Action[] getActions();
+
+ public ApplicationConnection getClient();
+
+ public String getPaintableId();
+
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client.ui;
+
+public class AlignmentInfo {
+
+ public static final int ALIGNMENT_LEFT = 1;
+ public static final int ALIGNMENT_RIGHT = 2;
+ public static final int ALIGNMENT_TOP = 4;
+ public static final int ALIGNMENT_BOTTOM = 8;
+ public static final int ALIGNMENT_HORIZONTAL_CENTER = 16;
+ public static final int ALIGNMENT_VERTICAL_CENTER = 32;
+
+ private int bitMask;
+
+ public AlignmentInfo(int bitMask) {
+ this.bitMask = bitMask;
+ }
+
+ public AlignmentInfo(int horizontal, int vertical) {
+ setAlignment(horizontal, vertical);
+ }
+
+ public void setAlignment(int horiz, int vert) {
+ bitMask = horiz + vert;
+ }
+
+ public int getBitMask() {
+ return bitMask;
+ }
+
+ public boolean isTop() {
+ return (bitMask & ALIGNMENT_TOP) == ALIGNMENT_TOP;
+ }
+
+ public boolean isBottom() {
+ return (bitMask & ALIGNMENT_BOTTOM) == ALIGNMENT_BOTTOM;
+ }
+
+ public boolean isLeft() {
+ return (bitMask & ALIGNMENT_LEFT) == ALIGNMENT_LEFT;
+ }
+
+ public boolean isRight() {
+ return (bitMask & ALIGNMENT_RIGHT) == ALIGNMENT_RIGHT;
+ }
+
+ public boolean isVerticalCenter() {
+ return (bitMask & ALIGNMENT_VERTICAL_CENTER) == ALIGNMENT_VERTICAL_CENTER;
+ }
+
+ public boolean isHorizontalCenter() {
+ return (bitMask & ALIGNMENT_HORIZONTAL_CENTER) == ALIGNMENT_HORIZONTAL_CENTER;
+ }
+
+ public String getVerticalAlignment() {
+ if (isBottom()) {
+ return "bottom";
+ } else if (isVerticalCenter()) {
+ return "middle";
+ }
+ return "top";
+ }
+
+ public String getHorizontalAlignment() {
+ if (isRight()) {
+ return "right";
+ } else if (isHorizontalCenter()) {
+ return "center";
+ }
+ return "left";
+ }
+
+}
--- /dev/null
+/* \r
+@ITMillApache2LicenseForJavaFiles@\r
+ */\r
+\r
+package com.itmill.toolkit.terminal.gwt.client.ui;\r
+\r
+import java.util.Date;\r
+\r
+import com.itmill.toolkit.terminal.gwt.client.DateTimeService;\r
+\r
+public class CalendarEntry {\r
+ private final String styleName;\r
+ private Date start;\r
+ private Date end;\r
+ private String title;\r
+ private String description;\r
+ private boolean notime;\r
+\r
+ public CalendarEntry(String styleName, Date start, Date end, String title,\r
+ String description, boolean notime) {\r
+ this.styleName = styleName;\r
+ if (notime) {\r
+ Date d = new Date(start.getTime());\r
+ d.setSeconds(0);\r
+ d.setMinutes(0);\r
+ this.start = d;\r
+ if (end != null) {\r
+ d = new Date(end.getTime());\r
+ d.setSeconds(0);\r
+ d.setMinutes(0);\r
+ this.end = d;\r
+ } else {\r
+ end = start;\r
+ }\r
+ } else {\r
+ this.start = start;\r
+ this.end = end;\r
+ }\r
+ this.title = title;\r
+ this.description = description;\r
+ this.notime = notime;\r
+ }\r
+\r
+ public CalendarEntry(String styleName, Date start, Date end, String title,\r
+ String description) {\r
+ this(styleName, start, end, title, description, false);\r
+ }\r
+\r
+ public String getStyleName() {\r
+ return styleName;\r
+ }\r
+\r
+ public Date getStart() {\r
+ return start;\r
+ }\r
+\r
+ public void setStart(Date start) {\r
+ this.start = start;\r
+ }\r
+\r
+ public Date getEnd() {\r
+ return end;\r
+ }\r
+\r
+ public void setEnd(Date end) {\r
+ this.end = end;\r
+ }\r
+\r
+ public String getTitle() {\r
+ return title;\r
+ }\r
+\r
+ public void setTitle(String title) {\r
+ this.title = title;\r
+ }\r
+\r
+ public String getDescription() {\r
+ return description;\r
+ }\r
+\r
+ public void setDescription(String description) {\r
+ this.description = description;\r
+ }\r
+\r
+ public boolean isNotime() {\r
+ return notime;\r
+ }\r
+\r
+ public void setNotime(boolean notime) {\r
+ this.notime = notime;\r
+ }\r
+\r
+ public String getStringForDate(Date d) {\r
+ // TODO format from DateTimeService\r
+ String s = "";\r
+ if (!notime) {\r
+ if (!DateTimeService.isSameDay(d, start)) {\r
+ s += (start.getYear() + 1900) + "." + (start.getMonth() + 1)\r
+ + "." + start.getDate() + " ";\r
+ }\r
+ int i = start.getHours();\r
+ s += (i < 10 ? "0" : "") + i;\r
+ s += ":";\r
+ i = start.getMinutes();\r
+ s += (i < 10 ? "0" : "") + i;\r
+ if (!start.equals(end)) {\r
+ s += " - ";\r
+ if (!DateTimeService.isSameDay(start, end)) {\r
+ s += (end.getYear() + 1900) + "." + (end.getMonth() + 1)\r
+ + "." + end.getDate() + " ";\r
+ }\r
+ i = end.getHours();\r
+ s += (i < 10 ? "0" : "") + i;\r
+ s += ":";\r
+ i = end.getMinutes();\r
+ s += (i < 10 ? "0" : "") + i;\r
+ }\r
+ s += " ";\r
+ }\r
+ if (title != null) {\r
+ s += title;\r
+ }\r
+ return s;\r
+ }\r
+\r
+}
\ No newline at end of file
--- /dev/null
+/* \r
+@ITMillApache2LicenseForJavaFiles@\r
+ */\r
+\r
+package com.itmill.toolkit.terminal.gwt.client.ui;\r
+\r
+import java.util.Date;\r
+import java.util.Iterator;\r
+import java.util.List;\r
+\r
+import com.google.gwt.user.client.DOM;\r
+import com.google.gwt.user.client.Event;\r
+import com.google.gwt.user.client.Timer;\r
+import com.google.gwt.user.client.ui.ClickListener;\r
+import com.google.gwt.user.client.ui.FlexTable;\r
+import com.google.gwt.user.client.ui.MouseListener;\r
+import com.google.gwt.user.client.ui.MouseListenerCollection;\r
+import com.google.gwt.user.client.ui.SourcesMouseEvents;\r
+import com.google.gwt.user.client.ui.SourcesTableEvents;\r
+import com.google.gwt.user.client.ui.TableListener;\r
+import com.google.gwt.user.client.ui.Widget;\r
+import com.itmill.toolkit.terminal.gwt.client.DateTimeService;\r
+import com.itmill.toolkit.terminal.gwt.client.LocaleService;\r
+\r
+public class CalendarPanel extends FlexTable implements MouseListener,\r
+ ClickListener {\r
+\r
+ private final IDateField datefield;\r
+\r
+ private IEventButton prevYear;\r
+\r
+ private IEventButton nextYear;\r
+\r
+ private IEventButton prevMonth;\r
+\r
+ private IEventButton nextMonth;\r
+\r
+ private Time time;\r
+\r
+ private Date minDate = null;\r
+\r
+ private Date maxDate = null;\r
+\r
+ private CalendarEntrySource entrySource;\r
+\r
+ /* Needed to identify resolution changes */\r
+ private int resolution = IDateField.RESOLUTION_YEAR;\r
+\r
+ /* Needed to identify locale changes */\r
+ private String locale = LocaleService.getDefaultLocale();\r
+\r
+ public CalendarPanel(IDateField parent) {\r
+ datefield = parent;\r
+ setStyleName(IDateField.CLASSNAME + "-calendarpanel");\r
+ // buildCalendar(true);\r
+ addTableListener(new DateClickListener(this));\r
+ }\r
+\r
+ public CalendarPanel(IDateField parent, Date min, Date max) {\r
+ datefield = parent;\r
+ setStyleName(IDateField.CLASSNAME + "-calendarpanel");\r
+ // buildCalendar(true);\r
+ addTableListener(new DateClickListener(this));\r
+\r
+ }\r
+\r
+ private void buildCalendar(boolean forceRedraw) {\r
+ final boolean needsMonth = datefield.getCurrentResolution() > IDateField.RESOLUTION_YEAR;\r
+ boolean needsBody = datefield.getCurrentResolution() >= IDateField.RESOLUTION_DAY;\r
+ final boolean needsTime = datefield.getCurrentResolution() >= IDateField.RESOLUTION_HOUR;\r
+ buildCalendarHeader(forceRedraw, needsMonth);\r
+ clearCalendarBody(!needsBody);\r
+ if (needsBody) {\r
+ buildCalendarBody();\r
+ }\r
+ if (needsTime) {\r
+ buildTime(forceRedraw);\r
+ } else if (time != null) {\r
+ remove(time);\r
+ time = null;\r
+ }\r
+ }\r
+\r
+ private void clearCalendarBody(boolean remove) {\r
+ if (!remove) {\r
+ for (int row = 2; row < 8; row++) {\r
+ for (int col = 0; col < 7; col++) {\r
+ setHTML(row, col, " ");\r
+ }\r
+ }\r
+ } else if (getRowCount() > 2) {\r
+ while (getRowCount() > 2) {\r
+ removeRow(2);\r
+ }\r
+ }\r
+ }\r
+\r
+ private void buildCalendarHeader(boolean forceRedraw, boolean needsMonth) {\r
+ if (forceRedraw) {\r
+ if (prevMonth == null) { // Only do once\r
+ prevYear = new IEventButton();\r
+ prevYear.setHTML("«");\r
+ prevYear.setStyleName("i-button-prevyear");\r
+ nextYear = new IEventButton();\r
+ nextYear.setHTML("»");\r
+ nextYear.setStyleName("i-button-nextyear");\r
+ prevYear.addMouseListener(this);\r
+ nextYear.addMouseListener(this);\r
+ prevYear.addClickListener(this);\r
+ nextYear.addClickListener(this);\r
+ setWidget(0, 0, prevYear);\r
+ setWidget(0, 4, nextYear);\r
+\r
+ if (needsMonth) {\r
+ prevMonth = new IEventButton();\r
+ prevMonth.setHTML("‹");\r
+ prevMonth.setStyleName("i-button-prevmonth");\r
+ nextMonth = new IEventButton();\r
+ nextMonth.setHTML("›");\r
+ nextMonth.setStyleName("i-button-nextmonth");\r
+ prevMonth.addMouseListener(this);\r
+ nextMonth.addMouseListener(this);\r
+ prevMonth.addClickListener(this);\r
+ nextMonth.addClickListener(this);\r
+ setWidget(0, 3, nextMonth);\r
+ setWidget(0, 1, prevMonth);\r
+ }\r
+\r
+ getFlexCellFormatter().setColSpan(0, 2, 3);\r
+ getRowFormatter().addStyleName(0,\r
+ IDateField.CLASSNAME + "-calendarpanel-header");\r
+ } else if (!needsMonth) {\r
+ // Remove month traverse buttons\r
+ prevMonth.removeClickListener(this);\r
+ prevMonth.removeMouseListener(this);\r
+ nextMonth.removeClickListener(this);\r
+ nextMonth.removeMouseListener(this);\r
+ remove(prevMonth);\r
+ remove(nextMonth);\r
+ prevMonth = null;\r
+ nextMonth = null;\r
+ }\r
+\r
+ // Print weekday names\r
+ final int firstDay = datefield.getDateTimeService()\r
+ .getFirstDayOfWeek();\r
+ for (int i = 0; i < 7; i++) {\r
+ int day = i + firstDay;\r
+ if (day > 6) {\r
+ day = 0;\r
+ }\r
+ if (datefield.getCurrentResolution() > IDateField.RESOLUTION_MONTH) {\r
+ setHTML(1, i, "<strong>"\r
+ + datefield.getDateTimeService().getShortDay(day)\r
+ + "</strong>");\r
+ } else {\r
+ setHTML(1, i, "");\r
+ }\r
+ }\r
+ }\r
+\r
+ final String monthName = needsMonth ? datefield.getDateTimeService()\r
+ .getMonth(datefield.getShowingDate().getMonth()) : "";\r
+ final int year = datefield.getShowingDate().getYear() + 1900;\r
+ setHTML(0, 2, "<span class=\"" + IDateField.CLASSNAME\r
+ + "-calendarpanel-month\">" + monthName + " " + year\r
+ + "</span>");\r
+ }\r
+\r
+ private void buildCalendarBody() {\r
+ // date actually selected?\r
+ Date currentDate = datefield.getCurrentDate();\r
+ Date showing = datefield.getShowingDate();\r
+ boolean selected = (currentDate != null\r
+ && currentDate.getMonth() == showing.getMonth() && currentDate\r
+ .getYear() == showing.getYear());\r
+\r
+ final int startWeekDay = datefield.getDateTimeService()\r
+ .getStartWeekDay(datefield.getShowingDate());\r
+ final int numDays = DateTimeService.getNumberOfDaysInMonth(datefield\r
+ .getShowingDate());\r
+ int dayCount = 0;\r
+ final Date today = new Date();\r
+ final Date curr = new Date(datefield.getShowingDate().getTime());\r
+ for (int row = 2; row < 8; row++) {\r
+ for (int col = 0; col < 7; col++) {\r
+ if (!(row == 2 && col < startWeekDay)) {\r
+ if (dayCount < numDays) {\r
+ final int selectedDate = ++dayCount;\r
+ String title = "";\r
+ if (entrySource != null) {\r
+ curr.setDate(dayCount);\r
+ final List entries = entrySource.getEntries(curr,\r
+ IDateField.RESOLUTION_DAY);\r
+ if (entries != null) {\r
+ for (final Iterator it = entries.iterator(); it\r
+ .hasNext();) {\r
+ final CalendarEntry entry = (CalendarEntry) it\r
+ .next();\r
+ title += (title.length() > 0 ? ", " : "")\r
+ + entry.getStringForDate(curr);\r
+ }\r
+ }\r
+ }\r
+ final String baseclass = IDateField.CLASSNAME\r
+ + "-calendarpanel-day";\r
+ String cssClass = baseclass;\r
+ if (!isEnabledDate(curr)) {\r
+ cssClass += " " + baseclass + "-disabled";\r
+ }\r
+ if (selected\r
+ && datefield.getShowingDate().getDate() == dayCount) {\r
+ cssClass += " " + baseclass + "-selected";\r
+ }\r
+ if (today.getDate() == dayCount\r
+ && today.getMonth() == datefield\r
+ .getShowingDate().getMonth()\r
+ && today.getYear() == datefield\r
+ .getShowingDate().getYear()) {\r
+ cssClass += " " + baseclass + "-today";\r
+ }\r
+ if (title.length() > 0) {\r
+ cssClass += " " + baseclass + "-entry";\r
+ }\r
+ setHTML(row, col, "<span title=\"" + title\r
+ + "\" class=\"" + cssClass + "\">"\r
+ + selectedDate + "</span>");\r
+ } else {\r
+ break;\r
+ }\r
+\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ private void buildTime(boolean forceRedraw) {\r
+ if (time == null) {\r
+ time = new Time(datefield);\r
+ setText(8, 0, ""); // Add new row\r
+ getFlexCellFormatter().setColSpan(8, 0, 7);\r
+ setWidget(8, 0, time);\r
+ }\r
+ time.updateTime(forceRedraw);\r
+ }\r
+\r
+ /**\r
+ * \r
+ * @param forceRedraw\r
+ * Build all from scratch, in case of e.g. locale changes\r
+ */\r
+ public void updateCalendar() {\r
+ // Locale and resolution changes force a complete redraw\r
+ buildCalendar(locale != datefield.getCurrentLocale()\r
+ || resolution != datefield.getCurrentResolution());\r
+ if (datefield instanceof ITextualDate) {\r
+ ((ITextualDate) datefield).buildDate();\r
+ }\r
+ locale = datefield.getCurrentLocale();\r
+ resolution = datefield.getCurrentResolution();\r
+ }\r
+\r
+ public void onClick(Widget sender) {\r
+ // processClickEvent(sender, true);\r
+ }\r
+\r
+ private boolean isEnabledDate(Date date) {\r
+ if ((minDate != null && date.before(minDate))\r
+ || (maxDate != null && date.after(maxDate))) {\r
+ return false;\r
+ }\r
+ return true;\r
+ }\r
+\r
+ private void processClickEvent(Widget sender, boolean updateVariable) {\r
+ if (!datefield.isEnabled() || datefield.isReadonly()) {\r
+ return;\r
+ }\r
+ Date showingDate = datefield.getShowingDate();\r
+ if (!updateVariable) {\r
+ if (sender == prevYear) {\r
+ showingDate.setYear(showingDate.getYear() - 1);\r
+ updateCalendar();\r
+ } else if (sender == nextYear) {\r
+ showingDate.setYear(showingDate.getYear() + 1);\r
+ updateCalendar();\r
+ } else if (sender == prevMonth) {\r
+ showingDate.setMonth(showingDate.getMonth() - 1);\r
+ updateCalendar();\r
+ } else if (sender == nextMonth) {\r
+ showingDate.setMonth(showingDate.getMonth() + 1);\r
+ updateCalendar();\r
+ }\r
+ } else {\r
+ if (datefield.getCurrentResolution() == IDateField.RESOLUTION_YEAR\r
+ || datefield.getCurrentResolution() == IDateField.RESOLUTION_MONTH) {\r
+ // Due to current UI, update variable if res=year/month\r
+ datefield.setCurrentDate(new Date(showingDate.getTime()));\r
+ if (datefield.getCurrentResolution() == IDateField.RESOLUTION_MONTH) {\r
+ datefield.getClient().updateVariable(datefield.getId(),\r
+ "month", datefield.getCurrentDate().getMonth() + 1,\r
+ false);\r
+ }\r
+ datefield.getClient().updateVariable(datefield.getId(), "year",\r
+ datefield.getCurrentDate().getYear() + 1900,\r
+ datefield.isImmediate());\r
+ }\r
+ }\r
+ }\r
+\r
+ private Timer timer;\r
+\r
+ public void onMouseDown(final Widget sender, int x, int y) {\r
+ if (sender instanceof IEventButton) {\r
+ processClickEvent(sender, false);\r
+ timer = new Timer() {\r
+ public void run() {\r
+ processClickEvent(sender, false);\r
+ }\r
+ };\r
+ timer.scheduleRepeating(100);\r
+ }\r
+ }\r
+\r
+ public void onMouseEnter(Widget sender) {\r
+ }\r
+\r
+ public void onMouseLeave(Widget sender) {\r
+ if (timer != null) {\r
+ timer.cancel();\r
+ }\r
+ }\r
+\r
+ public void onMouseMove(Widget sender, int x, int y) {\r
+ }\r
+\r
+ public void onMouseUp(Widget sender, int x, int y) {\r
+ if (timer != null) {\r
+ timer.cancel();\r
+ }\r
+ processClickEvent(sender, true);\r
+ }\r
+\r
+ private class IEventButton extends IButton implements SourcesMouseEvents {\r
+\r
+ private MouseListenerCollection mouseListeners;\r
+\r
+ public IEventButton() {\r
+ super();\r
+ sinkEvents(Event.FOCUSEVENTS | Event.KEYEVENTS | Event.ONCLICK\r
+ | Event.MOUSEEVENTS);\r
+ }\r
+\r
+ public void addMouseListener(MouseListener listener) {\r
+ if (mouseListeners == null) {\r
+ mouseListeners = new MouseListenerCollection();\r
+ }\r
+ mouseListeners.add(listener);\r
+ }\r
+\r
+ public void removeMouseListener(MouseListener listener) {\r
+ if (mouseListeners != null) {\r
+ mouseListeners.remove(listener);\r
+ }\r
+ }\r
+\r
+ public void onBrowserEvent(Event event) {\r
+ super.onBrowserEvent(event);\r
+ switch (DOM.eventGetType(event)) {\r
+ case Event.ONMOUSEDOWN:\r
+ case Event.ONMOUSEUP:\r
+ case Event.ONMOUSEMOVE:\r
+ case Event.ONMOUSEOVER:\r
+ case Event.ONMOUSEOUT:\r
+ if (mouseListeners != null) {\r
+ mouseListeners.fireMouseEvent(this, event);\r
+ }\r
+ break;\r
+ }\r
+ }\r
+ }\r
+\r
+ private class DateClickListener implements TableListener {\r
+\r
+ private final CalendarPanel cal;\r
+\r
+ public DateClickListener(CalendarPanel panel) {\r
+ cal = panel;\r
+ }\r
+\r
+ public void onCellClicked(SourcesTableEvents sender, int row, int col) {\r
+ if (sender != cal || row < 2 || row > 7\r
+ || !cal.datefield.isEnabled() || cal.datefield.isReadonly()) {\r
+ return;\r
+ }\r
+\r
+ final String text = cal.getText(row, col);\r
+ if (text.equals(" ")) {\r
+ return;\r
+ }\r
+\r
+ try {\r
+ final Integer day = new Integer(text);\r
+ final Date newDate = cal.datefield.getShowingDate();\r
+ newDate.setDate(day.intValue());\r
+ if (!isEnabledDate(newDate)) {\r
+ return;\r
+ }\r
+ if (cal.datefield.getCurrentDate() == null) {\r
+ cal.datefield.setCurrentDate(new Date(newDate.getTime()));\r
+\r
+ // Init variables with current time\r
+ datefield.getClient().updateVariable(cal.datefield.getId(),\r
+ "hour", newDate.getHours(), false);\r
+ datefield.getClient().updateVariable(cal.datefield.getId(),\r
+ "min", newDate.getMinutes(), false);\r
+ datefield.getClient().updateVariable(cal.datefield.getId(),\r
+ "sec", newDate.getSeconds(), false);\r
+ datefield.getClient().updateVariable(cal.datefield.getId(),\r
+ "msec", datefield.getMilliseconds(), false);\r
+ }\r
+\r
+ cal.datefield.getCurrentDate().setTime(newDate.getTime());\r
+ cal.datefield.getClient().updateVariable(cal.datefield.getId(),\r
+ "day", cal.datefield.getCurrentDate().getDate(), false);\r
+ cal.datefield.getClient().updateVariable(cal.datefield.getId(),\r
+ "month", cal.datefield.getCurrentDate().getMonth() + 1,\r
+ false);\r
+ cal.datefield.getClient().updateVariable(cal.datefield.getId(),\r
+ "year",\r
+ cal.datefield.getCurrentDate().getYear() + 1900,\r
+ cal.datefield.isImmediate());\r
+\r
+ if (datefield instanceof ITextualDate\r
+ && resolution < IDateField.RESOLUTION_HOUR) {\r
+ ((ToolkitOverlay) getParent()).hide();\r
+ } else {\r
+ updateCalendar();\r
+ }\r
+\r
+ } catch (final NumberFormatException e) {\r
+ // Not a number, ignore and stop here\r
+ return;\r
+ }\r
+ }\r
+\r
+ }\r
+\r
+ public void setLimits(Date min, Date max) {\r
+ if (min != null) {\r
+ final Date d = new Date(min.getTime());\r
+ d.setHours(0);\r
+ d.setMinutes(0);\r
+ d.setSeconds(1);\r
+ minDate = d;\r
+ } else {\r
+ minDate = null;\r
+ }\r
+ if (max != null) {\r
+ final Date d = new Date(max.getTime());\r
+ d.setHours(24);\r
+ d.setMinutes(59);\r
+ d.setSeconds(59);\r
+ maxDate = d;\r
+ } else {\r
+ maxDate = null;\r
+ }\r
+ }\r
+\r
+ public void setCalendarEntrySource(CalendarEntrySource entrySource) {\r
+ this.entrySource = entrySource;\r
+ }\r
+\r
+ public CalendarEntrySource getCalendarEntrySource() {\r
+ return entrySource;\r
+ }\r
+\r
+ public interface CalendarEntrySource {\r
+ public List getEntries(Date date, int resolution);\r
+ }\r
+\r
+ /**\r
+ * Sets focus to Calendar panel.\r
+ * \r
+ * @param focus\r
+ */\r
+ public void setFocus(boolean focus) {\r
+ nextYear.setFocus(focus);\r
+ }\r
+\r
+}\r
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client.ui;
+
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.MenuBar;
+import com.google.gwt.user.client.ui.MenuItem;
+import com.google.gwt.user.client.ui.PopupPanel;
+
+public class ContextMenu extends ToolkitOverlay {
+
+ private ActionOwner actionOwner;
+
+ private final CMenuBar menu = new CMenuBar();
+
+ private int left;
+
+ private int top;
+
+ /**
+ * This method should be used only by Client object as only one per client
+ * should exists. Request an instance via client.getContextMenu();
+ *
+ * @param cli
+ * to be set as an owner of menu
+ */
+ public ContextMenu() {
+ super(true);
+ setWidget(menu);
+ setStyleName("i-contextmenu");
+ }
+
+ /**
+ * Sets the element from which to build menu
+ *
+ * @param ao
+ */
+ public void setActionOwner(ActionOwner ao) {
+ actionOwner = ao;
+ }
+
+ /**
+ * Shows context menu at given location.
+ *
+ * @param left
+ * @param top
+ */
+ public void showAt(int left, int top) {
+ this.left = left;
+ this.top = top;
+ menu.clearItems();
+ final Action[] actions = actionOwner.getActions();
+ for (int i = 0; i < actions.length; i++) {
+ final Action a = actions[i];
+ menu.addItem(new MenuItem(a.getHTML(), true, a));
+ }
+
+ setPopupPositionAndShow(new PositionCallback() {
+ public void setPosition(int offsetWidth, int offsetHeight) {
+ // mac FF gets bad width due GWT popups overflow hacks,
+ // re-determine width
+ offsetWidth = menu.getOffsetWidth();
+ int left = ContextMenu.this.left;
+ int top = ContextMenu.this.top;
+ if (offsetWidth + left > Window.getClientWidth()) {
+ left = left - offsetWidth;
+ if (left < 0) {
+ left = 0;
+ }
+ }
+ if (offsetHeight + top > Window.getClientHeight()) {
+ top = top - offsetHeight;
+ if (top < 0) {
+ top = 0;
+ }
+ }
+ setPopupPosition(left, top);
+ }
+ });
+ }
+
+ public void showAt(ActionOwner ao, int left, int top) {
+ setActionOwner(ao);
+ showAt(left, top);
+ }
+
+ /**
+ * Extend standard Gwt MenuBar to set proper settings and to override
+ * onPopupClosed method so that PopupPanel gets closed.
+ */
+ class CMenuBar extends MenuBar {
+ public CMenuBar() {
+ super(true);
+ }
+
+ public void onPopupClosed(PopupPanel sender, boolean autoClosed) {
+ super.onPopupClosed(sender, autoClosed);
+ ContextMenu.this.hide();
+ }
+ }
+}
--- /dev/null
+/**\r
+ * \r
+ */\r
+package com.itmill.toolkit.terminal.gwt.client.ui;\r
+\r
+/**\r
+ * This interface indicates that the component is a Field (serverside), and\r
+ * wants (for instance) to automatically get the i-modified classname.\r
+ * \r
+ */\r
+public interface Field {\r
+\r
+}\r
--- /dev/null
+package com.itmill.toolkit.terminal.gwt.client.ui;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.DeferredCommand;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.ui.ClickListener;
+import com.google.gwt.user.client.ui.ComplexPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;
+import com.itmill.toolkit.terminal.gwt.client.Caption;
+import com.itmill.toolkit.terminal.gwt.client.ContainerResizedListener;
+import com.itmill.toolkit.terminal.gwt.client.Paintable;
+import com.itmill.toolkit.terminal.gwt.client.UIDL;
+import com.itmill.toolkit.terminal.gwt.client.Util;
+
+public class IAccordion extends ITabsheetBase implements
+ ContainerResizedListener {
+
+ public static final String CLASSNAME = "i-accordion";
+
+ private ArrayList stack = new ArrayList();
+
+ private Set paintables = new HashSet();
+
+ private String height;
+
+ public IAccordion() {
+ super(CLASSNAME);
+ // IE6 needs this to calculate offsetHeight correctly
+ if (Util.isIE6()) {
+ DOM.setStyleAttribute(getElement(), "zoom", "1");
+ }
+ }
+
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+ super.updateFromUIDL(uidl, client);
+ iLayout();
+ }
+
+ private StackItem getSelectedStack() {
+ if (stack.size() == 0) {
+ return null;
+ }
+ return (StackItem) stack.get(activeTabIndex);
+ }
+
+ protected void renderTab(UIDL tabUidl, int index, boolean selected) {
+ // TODO check indexes, now new tabs get placed last (changing tab order
+ // is not supported from server-side)
+
+ StackItem item = new StackItem(tabUidl);
+
+ if (stack.size() == 0) {
+ item.addStyleDependentName("first");
+ }
+
+ stack.add(item);
+ add(item, getElement());
+
+ if (selected) {
+ item.open();
+ item.setContent(tabUidl.getChildUIDL(0));
+ } else if (tabUidl.getChildCount() > 0) {
+ // updating a drawn child on hidden tab
+ Paintable paintable = client.getPaintable(tabUidl.getChildUIDL(0));
+ paintable.updateFromUIDL(tabUidl.getChildUIDL(0), client);
+ }
+ }
+
+ protected void selectTab(final int index, final UIDL contentUidl) {
+ StackItem item = (StackItem) stack.get(index);
+ if (index != activeTabIndex) {
+ activeTabIndex = index;
+ item.open();
+ iLayout();
+ }
+ item.setContent(contentUidl);
+ }
+
+ public void onSelectTab(StackItem item) {
+ final int index = stack.indexOf(item);
+ if (index != activeTabIndex && !disabled && !readonly
+ && !disabledTabKeys.contains(tabKeys.get(index))) {
+ if (getSelectedStack() != null) {
+ getSelectedStack().close();
+ }
+ addStyleDependentName("loading");
+ // run updating variables in deferred command to bypass some FF
+ // optimization issues
+ DeferredCommand.addCommand(new Command() {
+ public void execute() {
+ client.updateVariable(id, "selected", ""
+ + tabKeys.get(index), true);
+ }
+ });
+ }
+ }
+
+ public void setWidth(String width) {
+ if (width.equals("100%")) {
+ super.setWidth("");
+ } else {
+ super.setWidth(width);
+ }
+ }
+
+ public void setHeight(String height) {
+ this.height = height;
+ }
+
+ public void iLayout() {
+ StackItem item = getSelectedStack();
+ if (item == null) {
+ return;
+ }
+
+ if (height != null && height != "") {
+ // Detach visible widget from document flow for a while to calculate
+ // used height correctly
+ Widget w = item.getPaintable();
+ String originalPositioning = "";
+ if (w != null) {
+ originalPositioning = DOM.getStyleAttribute(w.getElement(),
+ "position");
+ DOM.setStyleAttribute(w.getElement(), "visibility", "hidden");
+ DOM.setStyleAttribute(w.getElement(), "position", "absolute");
+ }
+ DOM.setStyleAttribute(item.getContainerElement(), "height", "0");
+
+ // Calculate target height
+ super.setHeight(height);
+ int targetHeight = DOM.getElementPropertyInt(DOM
+ .getParent(getElement()), "offsetHeight");
+ super.setHeight("");
+
+ // Calculate used height
+ int usedHeight = getOffsetHeight();
+
+ int h = targetHeight - usedHeight;
+ if (h < 0) {
+ h = 0;
+ }
+ DOM.setStyleAttribute(item.getContainerElement(), "height", h
+ + "px");
+
+ // Put widget back into normal flow
+ if (w != null) {
+ DOM.setStyleAttribute(w.getElement(), "position",
+ originalPositioning);
+ DOM.setStyleAttribute(w.getElement(), "visibility", "");
+ }
+ } else {
+ DOM.setStyleAttribute(item.getContainerElement(), "height", "");
+ }
+
+ Util.runDescendentsLayout(this);
+ }
+
+ /**
+ * TODO Caption widget not properly attached
+ */
+ protected class StackItem extends ComplexPanel implements ClickListener {
+
+ private Caption caption;
+ private boolean open = false;
+ private Element content;
+ private Element captionNode;
+ private Paintable paintable;
+
+ public StackItem(UIDL tabUidl) {
+ setElement(DOM.createDiv());
+ caption = new Caption(null, client);
+ caption.addClickListener(this);
+ content = DOM.createDiv();
+ captionNode = DOM.createDiv();
+ super.add(caption, captionNode);
+ DOM.appendChild(captionNode, caption.getElement());
+ DOM.appendChild(getElement(), captionNode);
+ DOM.appendChild(getElement(), content);
+ setStylePrimaryName(CLASSNAME + "-item");
+ DOM.setElementProperty(content, "className", CLASSNAME
+ + "-item-content");
+ DOM.setElementProperty(captionNode, "className", CLASSNAME
+ + "-item-caption");
+ DOM.setStyleAttribute(content, "overflow", "auto");
+ DOM.setStyleAttribute(content, "display", "none");
+ // Force 'hasLayout' in IE6 (prevents layout problems)
+ if (Util.isIE6()) {
+ DOM.setStyleAttribute(content, "zoom", "1");
+ }
+
+ caption.updateCaption(tabUidl);
+ }
+
+ public Element getContainerElement() {
+ return content;
+ }
+
+ public Widget getPaintable() {
+ if (getWidgetCount() > 1) {
+ return getWidget(1);
+ } else {
+ return null;
+ }
+ }
+
+ public void open() {
+ open = true;
+ DOM.setStyleAttribute(content, "display", "");
+ addStyleDependentName("open");
+ if (getPaintable() != null) {
+ add(getPaintable(), content);
+ }
+ }
+
+ public void close() {
+ open = false;
+ if (getPaintable() != null) {
+ remove(getPaintable());
+ }
+ DOM.setStyleAttribute(content, "display", "none");
+ removeStyleDependentName("open");
+ }
+
+ public boolean isOpen() {
+ return open;
+ }
+
+ public void setContent(UIDL contentUidl) {
+ final Paintable newPntbl = client.getPaintable(contentUidl);
+ // due hack #1 in ITabsheetBase
+ ((Widget) newPntbl).setVisible(true);
+ if (getPaintable() == null) {
+ add((Widget) newPntbl, content);
+ paintables.add(newPntbl);
+ } else if (getPaintable() != newPntbl) {
+ client.unregisterPaintable((Paintable) getWidget(1));
+ paintables.remove(getWidget(1));
+ remove(1);
+ add((Widget) newPntbl, content);
+ paintables.add(newPntbl);
+ }
+ paintable = newPntbl;
+ paintable.updateFromUIDL(contentUidl, client);
+ }
+
+ public void onClick(Widget sender) {
+ onSelectTab(this);
+ }
+ }
+
+ protected void clearPaintables() {
+ stack.clear();
+ clear();
+ }
+
+ protected Iterator getPaintableIterator() {
+ return paintables.iterator();
+ }
+
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client.ui;
+
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.ClickListener;
+import com.google.gwt.user.client.ui.Widget;
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;
+import com.itmill.toolkit.terminal.gwt.client.Paintable;
+import com.itmill.toolkit.terminal.gwt.client.Tooltip;
+import com.itmill.toolkit.terminal.gwt.client.UIDL;
+
+public class IButton extends Button implements Paintable {
+
+ public static final String CLASSNAME = "i-button";
+
+ String id;
+
+ ApplicationConnection client;
+
+ private Element errorIndicatorElement;
+
+ private final Element captionElement = DOM.createSpan();
+
+ private Icon icon;
+
+ public IButton() {
+ setStyleName(CLASSNAME);
+
+ DOM.appendChild(getElement(), captionElement);
+
+ addClickListener(new ClickListener() {
+ public void onClick(Widget sender) {
+ if (id == null || client == null) {
+ return;
+ }
+ /*
+ * TODO isolata workaround. Safari don't always seem to fire
+ * onblur previously focused component before button is clicked.
+ */
+ IButton.this.setFocus(true);
+ client.updateVariable(id, "state", true, true);
+ }
+ });
+ sinkEvents(Tooltip.TOOLTIP_EVENTS);
+ }
+
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+
+ // Ensure correct implementation,
+ // but don't let container manage caption etc.
+ if (client.updateComponent(this, uidl, false)) {
+ return;
+ }
+
+ // Save details
+ this.client = client;
+ id = uidl.getId();
+
+ // Set text
+ setText(uidl.getStringAttribute("caption"));
+
+ // handle error
+ if (uidl.hasAttribute("error")) {
+ if (errorIndicatorElement == null) {
+ errorIndicatorElement = DOM.createDiv();
+ DOM.setElementProperty(errorIndicatorElement, "className",
+ "i-errorindicator");
+ }
+ DOM.insertChild(getElement(), errorIndicatorElement, 0);
+
+ } else if (errorIndicatorElement != null) {
+ DOM.removeChild(getElement(), errorIndicatorElement);
+ errorIndicatorElement = null;
+ }
+
+ if (uidl.hasAttribute("readonly")) {
+ setEnabled(false);
+ }
+
+ if (uidl.hasAttribute("icon")) {
+ if (icon == null) {
+ icon = new Icon(client);
+ DOM.insertChild(getElement(), icon.getElement(), 0);
+ }
+ icon.setUri(uidl.getStringAttribute("icon"));
+ } else {
+ if (icon != null) {
+ DOM.removeChild(getElement(), icon.getElement());
+ icon = null;
+ }
+ }
+ }
+
+ public void setText(String text) {
+ DOM.setInnerText(captionElement, text);
+ }
+
+ public void onBrowserEvent(Event event) {
+ super.onBrowserEvent(event);
+ if (client != null) {
+ client.handleTooltipEvent(event, this);
+ }
+ }
+
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client.ui;
+
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.ClickListener;
+import com.google.gwt.user.client.ui.Widget;
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;
+import com.itmill.toolkit.terminal.gwt.client.Paintable;
+import com.itmill.toolkit.terminal.gwt.client.Tooltip;
+import com.itmill.toolkit.terminal.gwt.client.UIDL;
+
+public class ICheckBox extends com.google.gwt.user.client.ui.CheckBox implements
+ Paintable, Field {
+
+ public static final String CLASSNAME = "i-checkbox";
+
+ String id;
+
+ boolean immediate;
+
+ ApplicationConnection client;
+
+ private Element errorIndicatorElement;
+
+ private Icon icon;
+
+ private boolean isBlockMode = false;
+
+ public ICheckBox() {
+ setStyleName(CLASSNAME);
+ addClickListener(new ClickListener() {
+
+ public void onClick(Widget sender) {
+ if (id == null || client == null) {
+ return;
+ }
+ client.updateVariable(id, "state", isChecked(), immediate);
+ }
+
+ });
+ sinkEvents(Tooltip.TOOLTIP_EVENTS);
+ }
+
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+ // Save details
+ this.client = client;
+ id = uidl.getId();
+
+ // Ensure correct implementation
+ if (client.updateComponent(this, uidl, false)) {
+ return;
+ }
+
+ if (uidl.hasAttribute("error")) {
+ if (errorIndicatorElement == null) {
+ errorIndicatorElement = DOM.createDiv();
+ DOM.setElementProperty(errorIndicatorElement, "className",
+ "i-errorindicator");
+ DOM.appendChild(getElement(), errorIndicatorElement);
+ }
+ } else if (errorIndicatorElement != null) {
+ DOM.setStyleAttribute(errorIndicatorElement, "display", "none");
+ }
+
+ if (uidl.hasAttribute("readonly")) {
+ setEnabled(false);
+ }
+
+ if (uidl.hasAttribute("icon")) {
+ if (icon == null) {
+ icon = new Icon(client);
+ DOM.insertChild(getElement(), icon.getElement(), 1);
+ }
+ icon.setUri(uidl.getStringAttribute("icon"));
+ } else if (icon != null) {
+ // detach icon
+ DOM.removeChild(getElement(), icon.getElement());
+ icon = null;
+ }
+
+ // Set text
+ setText(uidl.getStringAttribute("caption"));
+ setChecked(uidl.getBooleanVariable("state"));
+ immediate = uidl.getBooleanAttribute("immediate");
+ }
+
+ public void onBrowserEvent(Event event) {
+ super.onBrowserEvent(event);
+ if (client != null) {
+ client.handleTooltipEvent(event, this);
+ }
+ }
+
+ public void setWidth(String width) {
+ setBlockMode();
+ super.setWidth(width);
+ }
+
+ public void setHeight(String height) {
+ setBlockMode();
+ super.setHeight(height);
+ }
+
+ /**
+ * makes container element (span) to be block element to enable sizing.
+ */
+ private void setBlockMode() {
+ if (!isBlockMode) {
+ DOM.setStyleAttribute(getElement(), "display", "block");
+ isBlockMode = true;
+ }
+ }
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client.ui;
+
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;
+import com.itmill.toolkit.terminal.gwt.client.Container;
+import com.itmill.toolkit.terminal.gwt.client.Paintable;
+import com.itmill.toolkit.terminal.gwt.client.UIDL;
+
+public class ICustomComponent extends SimplePanel implements Container {
+
+ private static final String CLASSNAME = "i-customcomponent";
+
+ public ICustomComponent() {
+ super();
+ setStyleName(CLASSNAME);
+ }
+
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+ if (client.updateComponent(this, uidl, false)) {
+ return;
+ }
+
+ final UIDL child = uidl.getChildUIDL(0);
+ if (child != null) {
+ final Paintable p = client.getPaintable(child);
+ if (p != getWidget()) {
+ if (getWidget() != null) {
+ client.unregisterPaintable((Paintable) getWidget());
+ clear();
+ }
+ setWidget((Widget) p);
+ }
+ p.updateFromUIDL(child, client);
+ }
+
+ }
+
+ public boolean hasChildComponent(Widget component) {
+ if (getWidget() == component) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public void replaceChildComponent(Widget oldComponent, Widget newComponent) {
+ if (hasChildComponent(oldComponent)) {
+ clear();
+ setWidget(newComponent);
+ } else {
+ throw new IllegalStateException();
+ }
+ }
+
+ public void updateCaption(Paintable component, UIDL uidl) {
+ // TODO custom component could handle its composition roots caption
+ }
+
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client.ui;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.ui.ComplexPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;
+import com.itmill.toolkit.terminal.gwt.client.Caption;
+import com.itmill.toolkit.terminal.gwt.client.CaptionWrapper;
+import com.itmill.toolkit.terminal.gwt.client.Container;
+import com.itmill.toolkit.terminal.gwt.client.ContainerResizedListener;
+import com.itmill.toolkit.terminal.gwt.client.Paintable;
+import com.itmill.toolkit.terminal.gwt.client.UIDL;
+import com.itmill.toolkit.terminal.gwt.client.Util;
+
+/**
+ * Custom Layout implements complex layout defined with HTML template.
+ *
+ * @author IT Mill
+ *
+ */
+public class ICustomLayout extends ComplexPanel implements Paintable,
+ Container, ContainerResizedListener {
+
+ public static final String CLASSNAME = "i-customlayout";
+
+ /** Location-name to containing element in DOM map */
+ private final HashMap locationToElement = new HashMap();
+
+ /** Location-name to contained widget map */
+ private final HashMap locationToWidget = new HashMap();
+
+ /** Widget to captionwrapper map */
+ private final HashMap widgetToCaptionWrapper = new HashMap();
+
+ /** Currently rendered style */
+ String currentTemplate;
+
+ /** Unexecuted scripts loaded from the template */
+ private String scripts = "";
+
+ /** Paintable ID of this paintable */
+ private String pid;
+
+ private ApplicationConnection client;
+
+ public ICustomLayout() {
+ setElement(DOM.createDiv());
+ // Clear any unwanted styling
+ DOM.setStyleAttribute(getElement(), "border", "none");
+ DOM.setStyleAttribute(getElement(), "margin", "0");
+ DOM.setStyleAttribute(getElement(), "padding", "0");
+ setStyleName(CLASSNAME);
+ }
+
+ /**
+ * Sets widget to given location.
+ *
+ * If location already contains a widget it will be removed.
+ *
+ * @param widget
+ * Widget to be set into location.
+ * @param location
+ * location name where widget will be added
+ *
+ * @throws IllegalArgumentException
+ * if no such location is found in the layout.
+ */
+ public void setWidget(Widget widget, String location) {
+
+ if (widget == null) {
+ return;
+ }
+
+ // If no given location is found in the layout, and exception is throws
+ Element elem = (Element) locationToElement.get(location);
+ if (elem == null && hasTemplate()) {
+ throw new IllegalArgumentException("No location " + location
+ + " found");
+ }
+
+ // Get previous widget
+ final Widget previous = (Widget) locationToWidget.get(location);
+ // NOP if given widget already exists in this location
+ if (previous == widget) {
+ return;
+ }
+ remove(previous);
+
+ // if template is missing add element in order
+ if (!hasTemplate()) {
+ elem = getElement();
+ }
+
+ // Add widget to location
+ super.add(widget, elem);
+ locationToWidget.put(location, widget);
+ }
+
+ /** Update the layout from UIDL */
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+ this.client = client;
+ // Client manages general cases
+ if (client.updateComponent(this, uidl, false)) {
+ return;
+ }
+
+ // Update PID
+ pid = uidl.getId();
+ if (!hasTemplate()) {
+ // Update HTML template only once
+ initializeHTML(uidl, client);
+ }
+
+ // Set size
+ if (uidl.hasAttribute("width")) {
+ setWidth(uidl.getStringAttribute("width"));
+ } else {
+ setWidth("100%");
+ }
+ if (uidl.hasAttribute("height")) {
+ setHeight(uidl.getStringAttribute("height"));
+ } else {
+ setHeight("100%");
+ }
+
+ // Evaluate scripts
+ eval(scripts);
+ scripts = null;
+
+ iLayout();
+
+ Set oldWidgets = new HashSet();
+ oldWidgets.addAll(locationToWidget.values());
+
+ // For all contained widgets
+ for (final Iterator i = uidl.getChildIterator(); i.hasNext();) {
+ final UIDL uidlForChild = (UIDL) i.next();
+ if (uidlForChild.getTag().equals("location")) {
+ final String location = uidlForChild.getStringAttribute("name");
+ final Paintable child = client.getPaintable(uidlForChild
+ .getChildUIDL(0));
+ try {
+ setWidget((Widget) child, location);
+ child.updateFromUIDL(uidlForChild.getChildUIDL(0), client);
+ } catch (final IllegalArgumentException e) {
+ // If no location is found, this component is not visible
+ }
+ oldWidgets.remove(child);
+ }
+ }
+ for (Iterator iterator = oldWidgets.iterator(); iterator.hasNext();) {
+ Widget oldWidget = (Widget) iterator.next();
+ if (oldWidget.isAttached()) {
+ // slot of this widget is emptied, remove it
+ remove(oldWidget);
+ }
+ }
+
+ iLayout();
+ }
+
+ /** Initialize HTML-layout. */
+ private void initializeHTML(UIDL uidl, ApplicationConnection client) {
+
+ final String newTemplate = uidl.getStringAttribute("template");
+
+ // Get the HTML-template from client
+ String template = client
+ .getResource("layouts/" + newTemplate + ".html");
+ if (template == null) {
+ template = "<em>Layout file layouts/"
+ + newTemplate
+ + ".html is missing. Components will be drawn for debug purposes.</em>";
+ } else {
+ currentTemplate = newTemplate;
+ }
+
+ // Connect body of the template to DOM
+ template = extractBodyAndScriptsFromTemplate(template);
+ DOM.setInnerHTML(getElement(), template);
+
+ // Remap locations to elements
+ locationToElement.clear();
+ scanForLocations(getElement());
+
+ String themeUri = client.getThemeUri();
+ prefixImgSrcs(getElement(), themeUri + "/layouts/");
+
+ publishResizedFunction(DOM.getFirstChild(getElement()));
+
+ }
+
+ private native boolean uriEndsWithSlash()
+ /*-{
+ var path = $wnd.location.pathname;
+ if(path.charAt(path.length - 1) == "/")
+ return true;
+ return false;
+ }-*/;
+
+ private boolean hasTemplate() {
+ if (currentTemplate == null) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ /** Collect locations from template */
+ private void scanForLocations(Element elem) {
+
+ final String location = getLocation(elem);
+ if (location != null) {
+ locationToElement.put(location, elem);
+ DOM.setInnerHTML(elem, "");
+ } else {
+ final int len = DOM.getChildCount(elem);
+ for (int i = 0; i < len; i++) {
+ scanForLocations(DOM.getChild(elem, i));
+ }
+ }
+ }
+
+ /** Get the location attribute for given element */
+ private static native String getLocation(Element elem)
+ /*-{
+ return elem.getAttribute("location");
+ }-*/;
+
+ /** Evaluate given script in browser document */
+ private static native void eval(String script)
+ /*-{
+ try {
+ if (script != null)
+ eval("{ var document = $doc; var window = $wnd; "+ script + "}");
+ } catch (e) {
+ }
+ }-*/;
+
+ /** Prefix all img tag srcs with given prefix. */
+ private static native void prefixImgSrcs(Element e, String srcPrefix)
+ /*-{
+ try {
+ var divs = e.getElementsByTagName("img");
+ var base = "" + $doc.location;
+ var l = base.length-1;
+ while (l >= 0 && base.charAt(l) != "/") l--;
+ base = base.substring(0,l+1);
+ for (var i = 0; i < divs.length; i++) {
+ var div = divs[i];
+ var src = div.getAttribute("src");
+ if (src.indexOf("/")==0 || src.match(/\w+:\/\//)) {
+ continue;
+ }
+ div.setAttribute("src",srcPrefix + src);
+ }
+ } catch (e) { alert(e + " " + srcPrefix);}
+ }-*/;
+
+ /**
+ * Extract body part and script tags from raw html-template.
+ *
+ * Saves contents of all script-tags to private property: scripts. Returns
+ * contents of the body part for the html without script-tags. Also replaces
+ * all _UID_ tags with an unique id-string.
+ *
+ * @param html
+ * Original HTML-template received from server
+ * @return html that is used to create the HTMLPanel.
+ */
+ private String extractBodyAndScriptsFromTemplate(String html) {
+
+ // Replace UID:s
+ html = html.replaceAll("_UID_", pid + "__");
+
+ // Exctract script-tags
+ scripts = "";
+ int endOfPrevScript = 0;
+ int nextPosToCheck = 0;
+ String lc = html.toLowerCase();
+ String res = "";
+ int scriptStart = lc.indexOf("<script", nextPosToCheck);
+ while (scriptStart > 0) {
+ res += html.substring(endOfPrevScript, scriptStart);
+ scriptStart = lc.indexOf(">", scriptStart);
+ final int j = lc.indexOf("</script>", scriptStart);
+ scripts += html.substring(scriptStart + 1, j) + ";";
+ nextPosToCheck = endOfPrevScript = j + "</script>".length();
+ scriptStart = lc.indexOf("<script", nextPosToCheck);
+ }
+ res += html.substring(endOfPrevScript);
+
+ // Extract body
+ html = res;
+ lc = html.toLowerCase();
+ int startOfBody = lc.indexOf("<body");
+ if (startOfBody < 0) {
+ res = html;
+ } else {
+ res = "";
+ startOfBody = lc.indexOf(">", startOfBody) + 1;
+ final int endOfBody = lc.indexOf("</body>", startOfBody);
+ if (endOfBody > startOfBody) {
+ res = html.substring(startOfBody, endOfBody);
+ } else {
+ res = html.substring(startOfBody);
+ }
+ }
+
+ return res;
+ }
+
+ /** Replace child components */
+ public void replaceChildComponent(Widget from, Widget to) {
+ final String location = getLocation(from);
+ if (location == null) {
+ throw new IllegalArgumentException();
+ }
+ setWidget(to, location);
+ }
+
+ /** Does this layout contain given child */
+ public boolean hasChildComponent(Widget component) {
+ return locationToWidget.containsValue(component);
+ }
+
+ /** Update caption for given widget */
+ public void updateCaption(Paintable component, UIDL uidl) {
+ CaptionWrapper wrapper = (CaptionWrapper) widgetToCaptionWrapper
+ .get(component);
+ if (Caption.isNeeded(uidl)) {
+ if (wrapper == null) {
+ final String loc = getLocation((Widget) component);
+ super.remove((Widget) component);
+ wrapper = new CaptionWrapper(component, client);
+ super.add(wrapper, (Element) locationToElement.get(loc));
+ widgetToCaptionWrapper.put(component, wrapper);
+ }
+ wrapper.updateCaption(uidl);
+ } else {
+ if (wrapper != null) {
+ final String loc = getLocation((Widget) component);
+ super.remove(wrapper);
+ super.add((Widget) wrapper.getPaintable(),
+ (Element) locationToElement.get(loc));
+ widgetToCaptionWrapper.remove(component);
+ }
+ }
+ }
+
+ /** Get the location of an widget */
+ public String getLocation(Widget w) {
+ for (final Iterator i = locationToWidget.keySet().iterator(); i
+ .hasNext();) {
+ final String location = (String) i.next();
+ if (locationToWidget.get(location) == w) {
+ return location;
+ }
+ }
+ return null;
+ }
+
+ /** Removes given widget from the layout */
+ public boolean remove(Widget w) {
+ client.unregisterPaintable((Paintable) w);
+ final String location = getLocation(w);
+ if (location != null) {
+ locationToWidget.remove(location);
+ }
+ final CaptionWrapper cw = (CaptionWrapper) widgetToCaptionWrapper
+ .get(w);
+ if (cw != null) {
+ widgetToCaptionWrapper.remove(w);
+ return super.remove(cw);
+ } else if (w != null) {
+ return super.remove(w);
+ }
+ return false;
+ }
+
+ /** Adding widget without specifying location is not supported */
+ public void add(Widget w) {
+ throw new UnsupportedOperationException();
+ }
+
+ /** Clear all widgets from the layout */
+ public void clear() {
+ super.clear();
+ locationToWidget.clear();
+ widgetToCaptionWrapper.clear();
+ }
+
+ public void iLayout() {
+ if (!iLayoutJS(DOM.getFirstChild(getElement()))) {
+ Util.runDescendentsLayout(this);
+ }
+ }
+
+ /**
+ * This method is published to JS side with the same name into first DOM
+ * node of custom layout. This way if one implements some resizeable
+ * containers in custom layout he/she can notify children after resize.
+ */
+ public void notifyChildrenOfSizeChange() {
+ Util.runDescendentsLayout(this);
+ }
+
+ public void onDetach() {
+ detachResizedFunction(DOM.getFirstChild(getElement()));
+ }
+
+ private native void detachResizedFunction(Element element)
+ /*-{
+ element.notifyChildrenOfSizeChange = null;
+ }-*/;
+
+ private native void publishResizedFunction(Element element)
+ /*-{
+ var self = this;
+ element.notifyChildrenOfSizeChange = function() {
+ self.@com.itmill.toolkit.terminal.gwt.client.ui.ICustomLayout::notifyChildrenOfSizeChange()();
+ };
+ }-*/;
+
+ /**
+ * In custom layout one may want to run layout functions made with
+ * JavaScript. This function tests if one exists (with name "iLayoutJS" in
+ * layouts first DOM node) and runs et. Return value is used to determine if
+ * children needs to be notified of size changes.
+ *
+ * Note! When implementing a JS layout function you most likely want to call
+ * notifyChildrenOfSizeChange() function on your custom layouts main
+ * element. That method is used to control whether child components layout
+ * functions are to be run.
+ *
+ * @param el
+ * @return true if layout function exists and was run successfully, else
+ * false.
+ */
+ private native boolean iLayoutJS(Element el)
+ /*-{
+ if(el && el.iLayoutJS) {
+ try {
+ el.iLayoutJS();
+ return true;
+ } catch (e) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }-*/;
+}
--- /dev/null
+/* \r
+@ITMillApache2LicenseForJavaFiles@\r
+ */\r
+\r
+package com.itmill.toolkit.terminal.gwt.client.ui;\r
+\r
+import java.util.Date;\r
+\r
+import com.google.gwt.user.client.Event;\r
+import com.google.gwt.user.client.ui.FlowPanel;\r
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;\r
+import com.itmill.toolkit.terminal.gwt.client.DateTimeService;\r
+import com.itmill.toolkit.terminal.gwt.client.LocaleNotLoadedException;\r
+import com.itmill.toolkit.terminal.gwt.client.Paintable;\r
+import com.itmill.toolkit.terminal.gwt.client.Tooltip;\r
+import com.itmill.toolkit.terminal.gwt.client.UIDL;\r
+\r
+public class IDateField extends FlowPanel implements Paintable, Field {\r
+\r
+ public static final String CLASSNAME = "i-datefield";\r
+\r
+ protected String id;\r
+\r
+ protected ApplicationConnection client;\r
+\r
+ protected boolean immediate;\r
+\r
+ public static final int RESOLUTION_YEAR = 0;\r
+ public static final int RESOLUTION_MONTH = 1;\r
+ public static final int RESOLUTION_DAY = 2;\r
+ public static final int RESOLUTION_HOUR = 3;\r
+ public static final int RESOLUTION_MIN = 4;\r
+ public static final int RESOLUTION_SEC = 5;\r
+ public static final int RESOLUTION_MSEC = 6;\r
+\r
+ protected int currentResolution = RESOLUTION_YEAR;\r
+\r
+ protected String currentLocale;\r
+\r
+ protected boolean readonly;\r
+\r
+ protected boolean enabled;\r
+\r
+ protected Date date = null;\r
+ // e.g when paging a calendar, before actually selecting\r
+ protected Date showingDate = new Date();\r
+\r
+ protected DateTimeService dts;\r
+\r
+ public IDateField() {\r
+ setStyleName(CLASSNAME);\r
+ dts = new DateTimeService();\r
+ sinkEvents(Tooltip.TOOLTIP_EVENTS);\r
+ }\r
+\r
+ public void onBrowserEvent(Event event) {\r
+ super.onBrowserEvent(event);\r
+ if (client != null) {\r
+ client.handleTooltipEvent(event, this);\r
+ }\r
+ }\r
+\r
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {\r
+ // Ensure correct implementation and let layout manage caption\r
+ if (client.updateComponent(this, uidl, true)) {\r
+ return;\r
+ }\r
+\r
+ // Save details\r
+ this.client = client;\r
+ id = uidl.getId();\r
+ immediate = uidl.getBooleanAttribute("immediate");\r
+\r
+ readonly = uidl.getBooleanAttribute("readonly");\r
+ enabled = !uidl.getBooleanAttribute("disabled");\r
+\r
+ if (uidl.hasAttribute("locale")) {\r
+ final String locale = uidl.getStringAttribute("locale");\r
+ try {\r
+ dts.setLocale(locale);\r
+ currentLocale = locale;\r
+ } catch (final LocaleNotLoadedException e) {\r
+ currentLocale = dts.getLocale();\r
+ // TODO redirect this to console\r
+ System.out.println("Tried to use an unloaded locale \""\r
+ + locale + "\". Using default locale (" + currentLocale\r
+ + ").");\r
+ }\r
+ }\r
+\r
+ int newResolution;\r
+ if (uidl.hasVariable("msec")) {\r
+ newResolution = RESOLUTION_MSEC;\r
+ } else if (uidl.hasVariable("sec")) {\r
+ newResolution = RESOLUTION_SEC;\r
+ } else if (uidl.hasVariable("min")) {\r
+ newResolution = RESOLUTION_MIN;\r
+ } else if (uidl.hasVariable("hour")) {\r
+ newResolution = RESOLUTION_HOUR;\r
+ } else if (uidl.hasVariable("day")) {\r
+ newResolution = RESOLUTION_DAY;\r
+ } else if (uidl.hasVariable("month")) {\r
+ newResolution = RESOLUTION_MONTH;\r
+ } else {\r
+ newResolution = RESOLUTION_YEAR;\r
+ }\r
+\r
+ currentResolution = newResolution;\r
+\r
+ final int year = uidl.getIntVariable("year");\r
+ final int month = (currentResolution >= RESOLUTION_MONTH) ? uidl\r
+ .getIntVariable("month") : -1;\r
+ final int day = (currentResolution >= RESOLUTION_DAY) ? uidl\r
+ .getIntVariable("day") : -1;\r
+ final int hour = (currentResolution >= RESOLUTION_HOUR) ? uidl\r
+ .getIntVariable("hour") : 0;\r
+ final int min = (currentResolution >= RESOLUTION_MIN) ? uidl\r
+ .getIntVariable("min") : 0;\r
+ final int sec = (currentResolution >= RESOLUTION_SEC) ? uidl\r
+ .getIntVariable("sec") : 0;\r
+ final int msec = (currentResolution >= RESOLUTION_MSEC) ? uidl\r
+ .getIntVariable("msec") : 0;\r
+\r
+ // Construct new date for this datefield (only if not null)\r
+ if (year > -1) {\r
+ date = new Date((long) getTime(year, month, day, hour, min, sec,\r
+ msec));\r
+ showingDate.setTime(date.getTime());\r
+ } else {\r
+ date = null;\r
+ showingDate = new Date();\r
+ }\r
+\r
+ }\r
+\r
+ /*\r
+ * We need this redundant native function because Java's Date object doesn't\r
+ * have a setMilliseconds method.\r
+ */\r
+ private static native double getTime(int y, int m, int d, int h, int mi,\r
+ int s, int ms)\r
+ /*-{\r
+ try {\r
+ var date = new Date(2000,1,1,1); // don't use current date here\r
+ if(y && y >= 0) date.setFullYear(y);\r
+ if(m && m >= 1) date.setMonth(m-1);\r
+ if(d && d >= 0) date.setDate(d);\r
+ if(h >= 0) date.setHours(h);\r
+ if(mi >= 0) date.setMinutes(mi);\r
+ if(s >= 0) date.setSeconds(s);\r
+ if(ms >= 0) date.setMilliseconds(ms);\r
+ return date.getTime();\r
+ } catch (e) {\r
+ // TODO print some error message on the console\r
+ //console.log(e);\r
+ return (new Date()).getTime();\r
+ }\r
+ }-*/;\r
+\r
+ public int getMilliseconds() {\r
+ return (int) (date.getTime() - date.getTime() / 1000 * 1000);\r
+ }\r
+\r
+ public void setMilliseconds(int ms) {\r
+ date.setTime(date.getTime() / 1000 * 1000 + ms);\r
+ }\r
+\r
+ public int getShowingMilliseconds() {\r
+ return (int) (showingDate.getTime() - showingDate.getTime() / 1000 * 1000);\r
+ }\r
+\r
+ public void setShowingMilliseconds(int ms) {\r
+ showingDate.setTime(showingDate.getTime() / 1000 * 1000 + ms);\r
+ }\r
+\r
+ public int getCurrentResolution() {\r
+ return currentResolution;\r
+ }\r
+\r
+ public void setCurrentResolution(int currentResolution) {\r
+ this.currentResolution = currentResolution;\r
+ }\r
+\r
+ public String getCurrentLocale() {\r
+ return currentLocale;\r
+ }\r
+\r
+ public void setCurrentLocale(String currentLocale) {\r
+ this.currentLocale = currentLocale;\r
+ }\r
+\r
+ public Date getCurrentDate() {\r
+ return date;\r
+ }\r
+\r
+ public void setCurrentDate(Date date) {\r
+ this.date = date;\r
+ }\r
+\r
+ public Date getShowingDate() {\r
+ return showingDate;\r
+ }\r
+\r
+ public void setShowingDate(Date date) {\r
+ showingDate = date;\r
+ }\r
+\r
+ public boolean isImmediate() {\r
+ return immediate;\r
+ }\r
+\r
+ public boolean isReadonly() {\r
+ return readonly;\r
+ }\r
+\r
+ public boolean isEnabled() {\r
+ return enabled;\r
+ }\r
+\r
+ public DateTimeService getDateTimeService() {\r
+ return dts;\r
+ }\r
+\r
+ public String getId() {\r
+ return id;\r
+ }\r
+\r
+ public ApplicationConnection getClient() {\r
+ return client;\r
+ }\r
+}\r
--- /dev/null
+/* \r
+@ITMillApache2LicenseForJavaFiles@\r
+ */\r
+\r
+package com.itmill.toolkit.terminal.gwt.client.ui;\r
+\r
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;\r
+import com.itmill.toolkit.terminal.gwt.client.UIDL;\r
+\r
+public class IDateFieldCalendar extends IDateField {\r
+\r
+ private final CalendarPanel date;\r
+\r
+ public IDateFieldCalendar() {\r
+ super();\r
+ date = new CalendarPanel(this);\r
+ add(date);\r
+ }\r
+\r
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {\r
+ super.updateFromUIDL(uidl, client);\r
+ date.updateCalendar();\r
+ }\r
+\r
+}\r
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client.ui;
+
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.ui.HTML;
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;
+import com.itmill.toolkit.terminal.gwt.client.Paintable;
+import com.itmill.toolkit.terminal.gwt.client.UIDL;
+
+public class IEmbedded extends HTML implements Paintable {
+ private static String CLASSNAME = "i-embedded";
+
+ private String heigth;
+ private String width;
+ private Element browserElement;
+
+ public IEmbedded() {
+ setStyleName(CLASSNAME);
+ }
+
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+ if (client.updateComponent(this, uidl, true)) {
+ return;
+ }
+
+ boolean clearBrowserElement = true;
+
+ if (uidl.hasAttribute("type")) {
+ final String type = uidl.getStringAttribute("type");
+ if (type.equals("image")) {
+ String w = uidl.getStringAttribute("width");
+ if (w != null) {
+ w = " width=\"" + w + "\" ";
+ } else {
+ w = "";
+ }
+ String h = uidl.getStringAttribute("height");
+ if (h != null) {
+ h = " height=\"" + h + "\" ";
+ } else {
+ h = "";
+ }
+ setHTML("<img src=\"" + getSrc(uidl, client) + "\"" + w + h
+ + "/>");
+ client.addPngFix(DOM.getFirstChild(getElement()));
+
+ } else if (type.equals("browser")) {
+ if (browserElement == null) {
+ setHTML("<iframe width=\"100%\" height=\"100%\" frameborder=\"0\" src=\""
+ + getSrc(uidl, client) + "\"></iframe>");
+ browserElement = DOM.getFirstChild(getElement());
+ } else {
+ DOM.setElementAttribute(browserElement, "src", getSrc(uidl,
+ client));
+ }
+ clearBrowserElement = false;
+ } else {
+ ApplicationConnection.getConsole().log(
+ "Unknown Embedded type '" + type + "'");
+ }
+ } else if (uidl.hasAttribute("mimetype")) {
+ final String mime = uidl.getStringAttribute("mimetype");
+ if (mime.equals("application/x-shockwave-flash")) {
+ setHTML("<object width=\"" + width + "\" height=\"" + heigth
+ + "\"><param name=\"movie\" value=\""
+ + getSrc(uidl, client) + "\"><embed src=\""
+ + getSrc(uidl, client) + "\" width=\"" + width
+ + "\" height=\"" + heigth + "\"></embed></object>");
+ } else {
+ ApplicationConnection.getConsole().log(
+ "Unknown Embedded mimetype '" + mime + "'");
+ }
+ } else {
+ ApplicationConnection.getConsole().log(
+ "Unknown Embedded; no type or mimetype attribute");
+ }
+
+ if (clearBrowserElement) {
+ browserElement = null;
+ }
+
+ }
+
+ /**
+ * Helper to return translated src-attribute from embedded's UIDL
+ *
+ * @param uidl
+ * @param client
+ * @return
+ */
+ private String getSrc(UIDL uidl, ApplicationConnection client) {
+ String url = client.translateToolkitUri(uidl.getStringAttribute("src"));
+ if (url == null) {
+ return "";
+ }
+ return url;
+ }
+
+ public void setWidth(String width) {
+ if (width == null || width.equals("")) {
+ width = "100%";
+ }
+ this.width = width;
+ super.setHeight(width);
+ }
+
+ public void setHeight(String height) {
+ if (height == null || height.equals("")) {
+ height = "100%";
+ }
+ heigth = height;
+ super.setHeight(height);
+ }
+
+ protected void onDetach() {
+ // Force browser to fire unload event when component is detached from
+ // the view (IE doesn't do this automatically)
+ if (browserElement != null) {
+ DOM.setElementAttribute(browserElement, "src", "javascript:false");
+ }
+ super.onDetach();
+ }
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client.ui;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.ui.ComplexPanel;
+import com.google.gwt.user.client.ui.RootPanel;
+import com.google.gwt.user.client.ui.UIObject;
+import com.google.gwt.user.client.ui.Widget;
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;
+import com.itmill.toolkit.terminal.gwt.client.Caption;
+import com.itmill.toolkit.terminal.gwt.client.Container;
+import com.itmill.toolkit.terminal.gwt.client.ContainerResizedListener;
+import com.itmill.toolkit.terminal.gwt.client.Paintable;
+import com.itmill.toolkit.terminal.gwt.client.StyleConstants;
+import com.itmill.toolkit.terminal.gwt.client.UIDL;
+import com.itmill.toolkit.terminal.gwt.client.Util;
+
+/**
+ * @author IT Mill Ltd
+ */
+public class IExpandLayout extends ComplexPanel implements
+ ContainerResizedListener, Container {
+
+ public static final String CLASSNAME = "i-expandlayout";
+ public static final int ORIENTATION_HORIZONTAL = 1;
+
+ public static final int ORIENTATION_VERTICAL = 0;
+
+ /**
+ * Minimum pixels reserved for expanded element to avoid "odd" situations
+ * where expanded element is 0 size. Default is 5 pixels to show user a hint
+ * that there is a component. Then user can often use splitpanel or resize
+ * window to show component properly. This value may be insane in some
+ * applications. Override this to specify a proper for your case.
+ */
+ protected static final int EXPANDED_ELEMENTS_MIN_WIDTH = 5;
+
+ /**
+ * Contains reference to Element where Paintables are wrapped.
+ */
+ protected Element childContainer;
+
+ protected ApplicationConnection client;
+
+ protected HashMap componentToCaption = new HashMap();
+
+ /*
+ * Elements that provides the Layout interface implementation.
+ */
+ protected Element element;
+ private Widget expandedWidget;
+
+ private UIDL expandedWidgetUidl;
+
+ int orientationMode = ORIENTATION_VERTICAL;
+
+ protected int topMargin = -1;
+ private String width;
+ private String height;
+ private Element marginElement;
+ private Element breakElement;
+ private int bottomMargin = -1;
+ private boolean hasComponentSpacing;
+ private int spacingSize = -1;
+
+ public IExpandLayout() {
+ this(IExpandLayout.ORIENTATION_VERTICAL);
+ }
+
+ public IExpandLayout(int orientation) {
+ orientationMode = orientation;
+ constructDOM();
+ setStyleName(CLASSNAME);
+ }
+
+ public void add(Widget w) {
+ final WidgetWrapper wrapper = createWidgetWrappper();
+ DOM.appendChild(childContainer, wrapper.getElement());
+ super.add(w, wrapper.getContainerElement());
+ }
+
+ protected void constructDOM() {
+ element = DOM.createDiv();
+ // DOM.setStyleAttribute(element, "overflow", "hidden");
+
+ if (orientationMode == ORIENTATION_HORIZONTAL) {
+ marginElement = DOM.createDiv();
+ if (Util.isIE()) {
+ DOM.setStyleAttribute(marginElement, "zoom", "1");
+ DOM.setStyleAttribute(marginElement, "overflow", "hidden");
+ }
+ childContainer = DOM.createDiv();
+ if (Util.isIE()) {
+ DOM.setStyleAttribute(childContainer, "zoom", "1");
+ DOM.setStyleAttribute(childContainer, "overflow", "hidden");
+ }
+ DOM.setStyleAttribute(childContainer, "height", "100%");
+ breakElement = DOM.createDiv();
+ DOM.setStyleAttribute(breakElement, "overflow", "hidden");
+ DOM.setStyleAttribute(breakElement, "height", "0px");
+ DOM.setStyleAttribute(breakElement, "clear", "both");
+ DOM.appendChild(marginElement, childContainer);
+ DOM.appendChild(marginElement, breakElement);
+ DOM.appendChild(element, marginElement);
+ } else {
+ childContainer = DOM.createDiv();
+ DOM.appendChild(element, childContainer);
+ marginElement = childContainer;
+ }
+ setElement(element);
+ }
+
+ protected WidgetWrapper createWidgetWrappper() {
+ if (orientationMode == ORIENTATION_HORIZONTAL) {
+ return new HorizontalWidgetWrapper();
+ } else {
+ return new VerticalWidgetWrapper();
+ }
+ }
+
+ /**
+ * Returns given widgets WidgetWrapper
+ *
+ * @param child
+ * @return
+ */
+ public WidgetWrapper getWidgetWrapperFor(Widget child) {
+ final Element containerElement = DOM.getParent(child.getElement());
+ if (orientationMode == ORIENTATION_HORIZONTAL) {
+ return new HorizontalWidgetWrapper(containerElement);
+ } else {
+ return new VerticalWidgetWrapper(containerElement);
+ }
+ }
+
+ abstract class WidgetWrapper extends UIObject {
+ /**
+ * @return element that contains Widget
+ */
+ public Element getContainerElement() {
+ return getElement();
+ }
+
+ abstract void setExpandedSize(int pixels);
+
+ abstract void setAlignment(String verticalAlignment,
+ String horizontalAlignment);
+
+ abstract void setSpacingEnabled(boolean b);
+ }
+
+ class VerticalWidgetWrapper extends WidgetWrapper {
+
+ public VerticalWidgetWrapper(Element div) {
+ setElement(div);
+ }
+
+ public VerticalWidgetWrapper() {
+ setElement(DOM.createDiv());
+ // Set to 'hidden' at first (prevent IE6 content overflows), and set
+ // to 'auto' later.
+ DOM.setStyleAttribute(getContainerElement(), "overflow", "hidden");
+ }
+
+ void setExpandedSize(int pixels) {
+ final int spaceForMarginsAndSpacings = getOffsetHeight()
+ - DOM.getElementPropertyInt(getElement(), "clientHeight");
+ int fixedInnerSize = pixels - spaceForMarginsAndSpacings;
+ if (fixedInnerSize < 0) {
+ fixedInnerSize = 0;
+ }
+ setHeight(fixedInnerSize + "px");
+ DOM.setStyleAttribute(getContainerElement(), "overflow", "auto");
+ }
+
+ void setAlignment(String verticalAlignment, String horizontalAlignment) {
+ DOM.setStyleAttribute(getElement(), "textAlign",
+ horizontalAlignment);
+ // ignoring vertical alignment
+ }
+
+ void setSpacingEnabled(boolean b) {
+ setStyleName(getElement(), CLASSNAME + "-"
+ + StyleConstants.VERTICAL_SPACING, b);
+ }
+ }
+
+ class HorizontalWidgetWrapper extends WidgetWrapper {
+
+ private Element td;
+ private String valign = "top";
+ private String align = "left";
+
+ public HorizontalWidgetWrapper(Element element) {
+ if (DOM.getElementProperty(element, "nodeName").equals("TD")) {
+ td = element;
+ setElement(DOM.getParent(DOM.getParent(DOM.getParent(DOM
+ .getParent(td)))));
+ } else {
+ setElement(element);
+ }
+ }
+
+ public HorizontalWidgetWrapper() {
+ setElement(DOM.createDiv());
+ DOM.setStyleAttribute(getElement(), "cssFloat", "left");
+ if (Util.isIE()) {
+ DOM.setStyleAttribute(getElement(), "styleFloat", "left");
+ }
+ DOM.setStyleAttribute(getElement(), "height", "100%");
+ }
+
+ void setExpandedSize(int pixels) {
+ setWidth(pixels + "px");
+ DOM.setStyleAttribute(getElement(), "overflow", "hidden");
+ }
+
+ void setAlignment(String verticalAlignment, String horizontalAlignment) {
+ DOM.setStyleAttribute(getElement(), "verticalAlign",
+ verticalAlignment);
+ if (!valign.equals(verticalAlignment)) {
+ if (verticalAlignment.equals("top")) {
+ // remove table, move content to div
+
+ } else {
+ if (td == null) {
+ // build one cell table
+ final Element table = DOM.createTable();
+ final Element tBody = DOM.createTBody();
+ final Element tr = DOM.createTR();
+ td = DOM.createTD();
+ DOM.appendChild(table, tBody);
+ DOM.appendChild(tBody, tr);
+ DOM.appendChild(tr, td);
+ DOM.setElementProperty(table, "className", CLASSNAME
+ + "-valign");
+ DOM.setElementProperty(tr, "className", CLASSNAME
+ + "-valign");
+ DOM.setElementProperty(td, "className", CLASSNAME
+ + "-valign");
+ // move possible content to cell
+ final Element content = DOM.getFirstChild(getElement());
+ if (content != null) {
+ DOM.removeChild(getElement(), content);
+ DOM.appendChild(td, content);
+ }
+ DOM.appendChild(getElement(), table);
+ }
+ // set alignment
+ DOM.setStyleAttribute(td, "verticalAlign",
+ verticalAlignment);
+ }
+ valign = verticalAlignment;
+ }
+ if (!align.equals(horizontalAlignment)) {
+ DOM.setStyleAttribute(getContainerElement(), "textAlign",
+ horizontalAlignment);
+ align = horizontalAlignment;
+ }
+ }
+
+ public Element getContainerElement() {
+ if (td == null) {
+ return super.getContainerElement();
+ } else {
+ return td;
+ }
+ }
+
+ void setSpacingEnabled(boolean b) {
+ setStyleName(getElement(), CLASSNAME + "-"
+ + StyleConstants.HORIZONTAL_SPACING, b);
+ }
+ }
+
+ protected ArrayList getPaintables() {
+ final ArrayList al = new ArrayList();
+ final Iterator it = iterator();
+ while (it.hasNext()) {
+ final Widget w = (Widget) it.next();
+ if (w instanceof Paintable) {
+ al.add(w);
+ }
+ }
+ return al;
+ }
+
+ public Widget getWidget(int index) {
+ return getChildren().get(index);
+ }
+
+ public int getWidgetCount() {
+ return getChildren().size();
+ }
+
+ public int getWidgetIndex(Widget child) {
+ return getChildren().indexOf(child);
+ }
+
+ protected void handleAlignments(UIDL uidl) {
+ // Component alignments as a comma separated list.
+ // See com.itmill.toolkit.terminal.gwt.client.ui.AlignmentInfo.java for
+ // possible values.
+ final int[] alignments = uidl.getIntArrayAttribute("alignments");
+ int alignmentIndex = 0;
+ // Set alignment attributes
+ final Iterator it = getPaintables().iterator();
+ boolean first = true;
+ while (it.hasNext()) {
+ // Calculate alignment info
+ final AlignmentInfo ai = new AlignmentInfo(
+ alignments[alignmentIndex++]);
+ final WidgetWrapper wr = getWidgetWrapperFor((Widget) it.next());
+ wr.setAlignment(ai.getVerticalAlignment(), ai
+ .getHorizontalAlignment());
+ if (first) {
+ wr.setSpacingEnabled(false);
+ first = false;
+ } else {
+ wr.setSpacingEnabled(hasComponentSpacing);
+ }
+
+ }
+ }
+
+ protected void handleMargins(UIDL uidl) {
+ if (uidl.hasAttribute("margins")) {
+ final MarginInfo margins = new MarginInfo(uidl
+ .getIntAttribute("margins"));
+ setStyleName(marginElement, CLASSNAME + "-"
+ + StyleConstants.MARGIN_TOP, margins.hasTop());
+ setStyleName(marginElement, CLASSNAME + "-"
+ + StyleConstants.MARGIN_RIGHT, margins.hasRight());
+ setStyleName(marginElement, CLASSNAME + "-"
+ + StyleConstants.MARGIN_BOTTOM, margins.hasBottom());
+ setStyleName(marginElement, CLASSNAME + "-"
+ + StyleConstants.MARGIN_LEFT, margins.hasLeft());
+ }
+ }
+
+ public boolean hasChildComponent(Widget component) {
+ return getWidgetIndex(component) >= 0;
+ }
+
+ public void iLayout() {
+ if (orientationMode == ORIENTATION_HORIZONTAL) {
+ int pixels;
+ if ("".equals(height)) {
+ // try to find minimum height by looping all widgets
+ int maxHeight = 0;
+ Iterator iterator = getPaintables().iterator();
+ while (iterator.hasNext()) {
+ Widget w = (Widget) iterator.next();
+ int h = w.getOffsetHeight();
+ if (h > maxHeight) {
+ maxHeight = h;
+ }
+ }
+ pixels = maxHeight;
+ } else {
+ pixels = getOffsetHeight() - getTopMargin() - getBottomMargin();
+ if (pixels < 0) {
+ pixels = 0;
+ }
+ }
+ DOM.setStyleAttribute(marginElement, "height", pixels + "px");
+ DOM.setStyleAttribute(marginElement, "overflow", "hidden");
+ }
+
+ if (expandedWidget == null) {
+ return;
+ }
+
+ final int availableSpace = getAvailableSpace();
+
+ final int usedSpace = getUsedSpace();
+
+ int spaceForExpandedWidget = availableSpace - usedSpace;
+
+ if (spaceForExpandedWidget < EXPANDED_ELEMENTS_MIN_WIDTH) {
+ // TODO fire warning for developer
+ spaceForExpandedWidget = EXPANDED_ELEMENTS_MIN_WIDTH;
+ }
+
+ final WidgetWrapper wr = getWidgetWrapperFor(expandedWidget);
+ wr.setExpandedSize(spaceForExpandedWidget);
+
+ // TODO save previous size and only propagate if really changed
+ Util.runDescendentsLayout(this);
+ }
+
+ private int getTopMargin() {
+ if (topMargin < 0) {
+ topMargin = DOM.getElementPropertyInt(childContainer, "offsetTop")
+ - DOM.getElementPropertyInt(getElement(), "offsetTop");
+ }
+ if (topMargin < 0) {
+ // FIXME shouldn't happen
+ return 0;
+ } else {
+ return topMargin;
+ }
+ }
+
+ private int getBottomMargin() {
+ if (bottomMargin < 0) {
+ bottomMargin = DOM
+ .getElementPropertyInt(marginElement, "offsetTop")
+ + DOM.getElementPropertyInt(marginElement, "offsetHeight")
+ - DOM.getElementPropertyInt(breakElement, "offsetTop");
+ if (bottomMargin < 0) {
+ // FIXME shouldn't happen
+ return 0;
+ }
+ }
+ return bottomMargin;
+ }
+
+ private int getUsedSpace() {
+ int total = 0;
+ final int widgetCount = getWidgetCount();
+ final Iterator it = iterator();
+ while (it.hasNext()) {
+ final Widget w = (Widget) it.next();
+ if (w != expandedWidget) {
+ final WidgetWrapper wr = getWidgetWrapperFor(w);
+ if (orientationMode == ORIENTATION_VERTICAL) {
+ total += wr.getOffsetHeight();
+ } else {
+ total += wr.getOffsetWidth();
+ }
+ }
+ }
+ total += getSpacingSize() * (widgetCount - 1);
+ return total;
+ }
+
+ private int getSpacingSize() {
+ if (hasComponentSpacing) {
+ if (spacingSize < 0) {
+ final Element temp = DOM.createDiv();
+ final WidgetWrapper wr = createWidgetWrappper();
+ wr.setSpacingEnabled(true);
+ DOM.appendChild(temp, wr.getElement());
+ DOM.setStyleAttribute(temp, "position", "absolute");
+ DOM.setStyleAttribute(temp, "top", "0");
+ DOM.setStyleAttribute(temp, "visibility", "hidden");
+ DOM.appendChild(RootPanel.getBodyElement(), temp);
+ if (orientationMode == ORIENTATION_HORIZONTAL) {
+ spacingSize = DOM.getElementPropertyInt(wr.getElement(),
+ "offsetLeft");
+ } else {
+ spacingSize = DOM.getElementPropertyInt(wr.getElement(),
+ "offsetTop");
+ }
+ DOM.removeChild(RootPanel.getBodyElement(), temp);
+ }
+ return spacingSize;
+ } else {
+ return 0;
+ }
+ }
+
+ private int getAvailableSpace() {
+ int size;
+ if (orientationMode == ORIENTATION_VERTICAL) {
+ if (Util.isIE6()) {
+ DOM.setStyleAttribute(getElement(), "overflow", "hidden");
+ }
+ size = getOffsetHeight();
+ if (Util.isIE6()) {
+ DOM.setStyleAttribute(getElement(), "overflow", "visible");
+ }
+
+ final int marginTop = DOM.getElementPropertyInt(DOM
+ .getFirstChild(marginElement), "offsetTop")
+ - DOM.getElementPropertyInt(element, "offsetTop");
+
+ final Element lastElement = DOM.getChild(marginElement, (DOM
+ .getChildCount(marginElement) - 1));
+ final int marginBottom = DOM.getElementPropertyInt(marginElement,
+ "offsetHeight")
+ + DOM.getElementPropertyInt(marginElement, "offsetTop")
+ - (DOM.getElementPropertyInt(lastElement, "offsetTop") + DOM
+ .getElementPropertyInt(lastElement, "offsetHeight"));
+ size -= (marginTop + marginBottom);
+ } else {
+ // horizontal mode
+ size = DOM.getElementPropertyInt(childContainer, "offsetWidth");
+ }
+ return size;
+ }
+
+ protected void insert(Widget w, int beforeIndex) {
+ if (w instanceof Caption) {
+ final Caption c = (Caption) w;
+ // captions go into same container element as their
+ // owners
+ final Element container = DOM.getParent(((UIObject) c.getOwner())
+ .getElement());
+ final Element captionContainer = DOM.createDiv();
+ DOM.insertChild(container, captionContainer, 0);
+ insert(w, captionContainer, beforeIndex, false);
+ } else {
+ final WidgetWrapper wrapper = createWidgetWrappper();
+ DOM.insertChild(childContainer, wrapper.getElement(), beforeIndex);
+ insert(w, wrapper.getContainerElement(), beforeIndex, false);
+ }
+ }
+
+ public boolean remove(int index) {
+ return remove(getWidget(index));
+ }
+
+ public boolean remove(Widget w) {
+ final WidgetWrapper ww = getWidgetWrapperFor(w);
+ final boolean removed = super.remove(w);
+ if (removed) {
+ if (!(w instanceof Caption)) {
+ DOM.removeChild(childContainer, ww.getElement());
+ }
+ return true;
+ }
+ return false;
+ }
+
+ public void removeCaption(Widget w) {
+ final Caption c = (Caption) componentToCaption.get(w);
+ if (c != null) {
+ this.remove(c);
+ componentToCaption.remove(w);
+ }
+ }
+
+ public boolean removePaintable(Paintable p) {
+ final Caption c = (Caption) componentToCaption.get(p);
+ if (c != null) {
+ componentToCaption.remove(c);
+ remove(c);
+ }
+ client.unregisterPaintable(p);
+ if (expandedWidget == p) {
+ expandedWidget = null;
+ }
+ return remove((Widget) p);
+ }
+
+ public void replaceChildComponent(Widget from, Widget to) {
+ client.unregisterPaintable((Paintable) from);
+ final Caption c = (Caption) componentToCaption.get(from);
+ if (c != null) {
+ remove(c);
+ componentToCaption.remove(c);
+ }
+ final int index = getWidgetIndex(from);
+ if (index >= 0) {
+ remove(index);
+ insert(to, index);
+ }
+ }
+
+ public void updateCaption(Paintable component, UIDL uidl) {
+
+ Caption c = (Caption) componentToCaption.get(component);
+
+ if (Caption.isNeeded(uidl)) {
+ if (c == null) {
+ final int index = getWidgetIndex((Widget) component);
+ c = new Caption(component, client);
+ insert(c, index);
+ componentToCaption.put(component, c);
+ }
+ c.updateCaption(uidl);
+ } else {
+ if (c != null) {
+ remove(c);
+ componentToCaption.remove(component);
+ }
+ }
+ }
+
+ public void setWidth(String newWidth) {
+ if (newWidth.equals(width)) {
+ return;
+ }
+ width = newWidth;
+ super.setWidth(width);
+ }
+
+ public void setHeight(String newHeight) {
+ if (newHeight.equals(height)) {
+ return;
+ }
+ height = newHeight;
+ super.setHeight(height);
+ if (orientationMode == ORIENTATION_HORIZONTAL) {
+ iLayout();
+ }
+ }
+
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+
+ this.client = client;
+
+ // Modify layout margins
+ handleMargins(uidl);
+
+ // Ensure correct implementation
+ if (client.updateComponent(this, uidl, false)) {
+ return;
+ }
+
+ hasComponentSpacing = uidl.getBooleanAttribute("spacing");
+
+ final ArrayList uidlWidgets = new ArrayList();
+ for (final Iterator it = uidl.getChildIterator(); it.hasNext();) {
+ final UIDL cellUidl = (UIDL) it.next();
+ final Paintable child = client.getPaintable(cellUidl
+ .getChildUIDL(0));
+ uidlWidgets.add(child);
+ if (cellUidl.hasAttribute("expanded")) {
+ expandedWidget = (Widget) child;
+ expandedWidgetUidl = cellUidl.getChildUIDL(0);
+ }
+ }
+
+ final ArrayList oldWidgets = getPaintables();
+
+ final Iterator oldIt = oldWidgets.iterator();
+ final Iterator newIt = uidlWidgets.iterator();
+ final Iterator newUidl = uidl.getChildIterator();
+
+ Widget oldChild = null;
+ while (newIt.hasNext()) {
+ final Widget child = (Widget) newIt.next();
+ final UIDL childUidl = ((UIDL) newUidl.next()).getChildUIDL(0);
+ if (oldChild == null && oldIt.hasNext()) {
+ // search for next old Paintable which still exists in layout
+ // and delete others
+ while (oldIt.hasNext()) {
+ oldChild = (Widget) oldIt.next();
+ // now oldChild is an instance of Paintable
+ if (uidlWidgets.contains(oldChild)) {
+ break;
+ } else {
+ removePaintable((Paintable) oldChild);
+ oldChild = null;
+ }
+ }
+ }
+ if (oldChild == null) {
+ // we are adding components to layout
+ add(child);
+ } else if (child == oldChild) {
+ // child already attached and updated
+ oldChild = null;
+ } else if (hasChildComponent(child)) {
+ // current child has been moved, re-insert before current
+ // oldChild
+ // TODO this might be optimized by moving only container element
+ // to correct position
+ removeCaption(child);
+ int index = getWidgetIndex(oldChild);
+ if (componentToCaption.containsKey(oldChild)) {
+ index--;
+ }
+ remove(child);
+ insert(child, index);
+ } else {
+ // insert new child before old one
+ final int index = getWidgetIndex(oldChild);
+ insert(child, index);
+ }
+ if (child != expandedWidget) {
+ ((Paintable) child).updateFromUIDL(childUidl, client);
+ }
+ }
+ // remove possibly remaining old Paintable object which were not updated
+ while (oldIt.hasNext()) {
+ oldChild = (Widget) oldIt.next();
+ final Paintable p = (Paintable) oldChild;
+ if (!uidlWidgets.contains(p)) {
+ removePaintable(p);
+ }
+ }
+
+ if (uidlWidgets.size() == 0) {
+ return;
+ }
+
+ // Set component alignments
+ handleAlignments(uidl);
+
+ iLayout();
+
+ /*
+ * Expanded widget is updated after layout function so it has its
+ * container fixed at the moment of updateFromUIDL.
+ */
+ if (expandedWidget != null) {
+ ((Paintable) expandedWidget).updateFromUIDL(expandedWidgetUidl,
+ client);
+ }
+ }
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client.ui;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Iterator;
+
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.ClickListener;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.FocusListener;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.Image;
+import com.google.gwt.user.client.ui.KeyboardListener;
+import com.google.gwt.user.client.ui.PopupListener;
+import com.google.gwt.user.client.ui.PopupPanel;
+import com.google.gwt.user.client.ui.TextBox;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.gwt.user.client.ui.PopupPanel.PositionCallback;
+import com.google.gwt.user.client.ui.SuggestOracle.Suggestion;
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;
+import com.itmill.toolkit.terminal.gwt.client.Focusable;
+import com.itmill.toolkit.terminal.gwt.client.Paintable;
+import com.itmill.toolkit.terminal.gwt.client.Tooltip;
+import com.itmill.toolkit.terminal.gwt.client.UIDL;
+import com.itmill.toolkit.terminal.gwt.client.Util;
+
+/**
+ *
+ * TODO needs major refactoring (to be extensible etc)
+ */
+public class IFilterSelect extends Composite implements Paintable, Field,
+ KeyboardListener, ClickListener, FocusListener, Focusable {
+
+ public class FilterSelectSuggestion implements Suggestion, Command {
+
+ private final String key;
+ private final String caption;
+ private String iconUri;
+
+ public FilterSelectSuggestion(UIDL uidl) {
+ key = uidl.getStringAttribute("key");
+ caption = uidl.getStringAttribute("caption");
+ if (uidl.hasAttribute("icon")) {
+ iconUri = client.translateToolkitUri(uidl
+ .getStringAttribute("icon"));
+ }
+ }
+
+ public String getDisplayString() {
+ final StringBuffer sb = new StringBuffer();
+ if (iconUri != null) {
+ sb.append("<img src=\"");
+ sb.append(iconUri);
+ sb.append("\" alt=\"icon\" class=\"i-icon\" />");
+ }
+ sb.append(Util.escapeHTML(caption));
+ return sb.toString();
+ }
+
+ public String getReplacementString() {
+ return caption;
+ }
+
+ public int getOptionKey() {
+ return Integer.parseInt(key);
+ }
+
+ public String getIconUri() {
+ return iconUri;
+ }
+
+ public void execute() {
+ onSuggestionSelected(this);
+ }
+ }
+
+ /**
+ * @author mattitahvonen
+ *
+ */
+ public class SuggestionPopup extends ToolkitOverlay implements
+ PositionCallback, PopupListener {
+ private static final int EXTRASPACE = 8;
+
+ private static final String Z_INDEX = "30000";
+
+ private final SuggestionMenu menu;
+
+ private final Element up = DOM.createDiv();
+ private final Element down = DOM.createDiv();
+ private final Element status = DOM.createDiv();
+
+ private boolean isPagingEnabled = true;
+
+ private long lastAutoClosed;
+
+ SuggestionPopup() {
+ super(true);
+ menu = new SuggestionMenu();
+ setWidget(menu);
+ setStyleName(CLASSNAME + "-suggestpopup");
+ DOM.setStyleAttribute(getElement(), "zIndex", Z_INDEX);
+
+ final Element root = getContainerElement();
+
+ DOM.setInnerHTML(up, "<span>Prev</span>");
+ DOM.sinkEvents(up, Event.ONCLICK);
+ DOM.setInnerHTML(down, "<span>Next</span>");
+ DOM.sinkEvents(down, Event.ONCLICK);
+ DOM.insertChild(root, up, 0);
+ DOM.appendChild(root, down);
+ DOM.appendChild(root, status);
+ DOM.setElementProperty(status, "className", CLASSNAME + "-status");
+
+ addPopupListener(this);
+ }
+
+ public void showSuggestions(Collection currentSuggestions,
+ int currentPage, int totalSuggestions) {
+
+ if (ApplicationConnection.isTestingMode()) {
+ // Add TT anchor point
+ DOM.setElementProperty(getElement(), "id", paintableId
+ + "_OPTIONLIST");
+ }
+
+ menu.setSuggestions(currentSuggestions);
+ final int x = IFilterSelect.this.getAbsoluteLeft();
+ int y = tb.getAbsoluteTop();
+ y += tb.getOffsetHeight();
+ setPopupPosition(x, y);
+ final int first = currentPage * PAGELENTH
+ + (nullSelectionAllowed && currentPage > 0 ? 0 : 1);
+ final int last = first + currentSuggestions.size() - 1;
+ final int matches = totalSuggestions
+ - (nullSelectionAllowed ? 1 : 0);
+ if (last > 0) {
+ // nullsel not counted, as requested by user
+ DOM.setInnerText(status, (matches == 0 ? 0 : first)
+ + "-"
+ + ("".equals(lastFilter) && nullSelectionAllowed
+ && currentPage == 0 ? last - 1 : last) + "/"
+ + matches);
+ } else {
+ DOM.setInnerText(status, "");
+ }
+ setPrevButtonActive(first > 1);
+ setNextButtonActive(last < matches);
+
+ // clear previously fixed width
+ menu.setWidth("");
+ DOM.setStyleAttribute(DOM.getFirstChild(menu.getElement()),
+ "width", "");
+
+ setPopupPositionAndShow(this);
+ }
+
+ private void setNextButtonActive(boolean b) {
+ if (b) {
+ DOM.sinkEvents(down, Event.ONCLICK);
+ DOM.setElementProperty(down, "className", CLASSNAME
+ + "-nextpage");
+ } else {
+ DOM.sinkEvents(down, 0);
+ DOM.setElementProperty(down, "className", CLASSNAME
+ + "-nextpage-off");
+ }
+ }
+
+ private void setPrevButtonActive(boolean b) {
+ if (b) {
+ DOM.sinkEvents(up, Event.ONCLICK);
+ DOM
+ .setElementProperty(up, "className", CLASSNAME
+ + "-prevpage");
+ } else {
+ DOM.sinkEvents(up, 0);
+ DOM.setElementProperty(up, "className", CLASSNAME
+ + "-prevpage-off");
+ }
+
+ }
+
+ public void selectNextItem() {
+ final MenuItem cur = menu.getSelectedItem();
+ final int index = 1 + menu.getItems().indexOf(cur);
+ if (menu.getItems().size() > index) {
+ final MenuItem newSelectedItem = (MenuItem) menu.getItems()
+ .get(index);
+ menu.selectItem(newSelectedItem);
+ tb.setText(newSelectedItem.getText());
+ tb.setSelectionRange(lastFilter.length(), newSelectedItem
+ .getText().length()
+ - lastFilter.length());
+
+ } else if (hasNextPage()) {
+ filterOptions(currentPage + 1, lastFilter);
+ }
+ }
+
+ public void selectPrevItem() {
+ final MenuItem cur = menu.getSelectedItem();
+ final int index = -1 + menu.getItems().indexOf(cur);
+ if (index > -1) {
+ final MenuItem newSelectedItem = (MenuItem) menu.getItems()
+ .get(index);
+ menu.selectItem(newSelectedItem);
+ tb.setText(newSelectedItem.getText());
+ tb.setSelectionRange(lastFilter.length(), newSelectedItem
+ .getText().length()
+ - lastFilter.length());
+ } else if (index == -1) {
+ if (currentPage > 0) {
+ filterOptions(currentPage - 1, lastFilter);
+ }
+ } else {
+ final MenuItem newSelectedItem = (MenuItem) menu.getItems()
+ .get(menu.getItems().size() - 1);
+ menu.selectItem(newSelectedItem);
+ tb.setText(newSelectedItem.getText());
+ tb.setSelectionRange(lastFilter.length(), newSelectedItem
+ .getText().length()
+ - lastFilter.length());
+ }
+ }
+
+ public void onBrowserEvent(Event event) {
+ final Element target = DOM.eventGetTarget(event);
+ if (DOM.compare(target, up)
+ || DOM.compare(target, DOM.getChild(up, 0))) {
+ filterOptions(currentPage - 1, lastFilter);
+ } else if (DOM.compare(target, down)
+ || DOM.compare(target, DOM.getChild(down, 0))) {
+ filterOptions(currentPage + 1, lastFilter);
+ }
+ tb.setFocus(true);
+ }
+
+ public void setPagingEnabled(boolean paging) {
+ if (isPagingEnabled == paging) {
+ return;
+ }
+ if (paging) {
+ DOM.setStyleAttribute(down, "display", "block");
+ DOM.setStyleAttribute(up, "display", "block");
+ DOM.setStyleAttribute(status, "display", "block");
+ } else {
+ DOM.setStyleAttribute(down, "display", "none");
+ DOM.setStyleAttribute(up, "display", "none");
+ DOM.setStyleAttribute(status, "display", "none");
+ }
+ isPagingEnabled = paging;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.google.gwt.user.client.ui.PopupPanel$PositionCallback#setPosition(int,
+ * int)
+ */
+ public void setPosition(int offsetWidth, int offsetHeight) {
+
+ int top = -1;
+ int left = -1;
+
+ // reset menu size and retrieve its "natural" size
+ menu.setHeight("");
+ if (currentPage > 0) {
+ // fix height to avoid height change when getting to last page
+ menu.fixHeightTo(PAGELENTH);
+ }
+ offsetHeight = getOffsetHeight();
+
+ final int desiredWidth = IFilterSelect.this.getOffsetWidth();
+ int naturalMenuWidth = DOM.getElementPropertyInt(DOM
+ .getFirstChild(menu.getElement()), "offsetWidth");
+ if (naturalMenuWidth < desiredWidth) {
+ menu.setWidth(desiredWidth + "px");
+ DOM.setStyleAttribute(DOM.getFirstChild(menu.getElement()),
+ "width", "100%");
+ naturalMenuWidth = desiredWidth;
+ }
+ if (Util.isIE()) {
+ DOM.setStyleAttribute(getElement(), "width", naturalMenuWidth
+ + "px");
+ }
+
+ if (offsetHeight + getPopupTop() > Window.getClientHeight()
+ + Window.getScrollTop()) {
+ // popup on top of input instead
+ top = getPopupTop() - offsetHeight
+ - IFilterSelect.this.getOffsetHeight();
+ if (top < 0) {
+ top = 0;
+ }
+ } else {
+ top = getPopupTop();
+ }
+
+ // fetch real width (mac FF bugs here due GWT popups overflow:auto )
+ offsetWidth = DOM.getElementPropertyInt(DOM.getFirstChild(menu
+ .getElement()), "offsetWidth");
+ if (offsetWidth + getPopupLeft() > Window.getClientWidth()
+ + Window.getScrollLeft()) {
+ left = IFilterSelect.this.getAbsoluteLeft()
+ + IFilterSelect.this.getOffsetWidth()
+ + Window.getScrollLeft() - offsetWidth;
+ if (left < 0) {
+ left = 0;
+ }
+ } else {
+ left = getPopupLeft();
+ }
+ setPopupPosition(left, top);
+
+ }
+
+ /**
+ * @return true if popup was just closed
+ */
+ public boolean isJustClosed() {
+ final long now = (new Date()).getTime();
+ return (lastAutoClosed > 0 && (now - lastAutoClosed) < 200);
+ }
+
+ public void onPopupClosed(PopupPanel sender, boolean autoClosed) {
+ if (autoClosed) {
+ lastAutoClosed = (new Date()).getTime();
+ }
+ }
+
+ }
+
+ public class SuggestionMenu extends MenuBar {
+
+ SuggestionMenu() {
+ super(true);
+ setStyleName(CLASSNAME + "-suggestmenu");
+ }
+
+ /**
+ * Fixes menus height to use same space as full page would use. Needed
+ * to avoid height changes when quickly "scrolling" to last page
+ */
+ public void fixHeightTo(int pagelenth) {
+ if (currentSuggestions.size() > 0) {
+ final int pixels = pagelenth * (getOffsetHeight() - 2)
+ / currentSuggestions.size();
+ setHeight((pixels + 2) + "px");
+ }
+ }
+
+ public void setSuggestions(Collection suggestions) {
+ clearItems();
+ final Iterator it = suggestions.iterator();
+ while (it.hasNext()) {
+ final FilterSelectSuggestion s = (FilterSelectSuggestion) it
+ .next();
+ final MenuItem mi = new MenuItem(s.getDisplayString(), true, s);
+ this.addItem(mi);
+ if (s == currentSuggestion) {
+ selectItem(mi);
+ }
+ }
+ }
+
+ public void doSelectedItemAction() {
+ final MenuItem item = getSelectedItem();
+ final String enteredItemValue = tb.getText();
+ // check for exact match in menu
+ int p = getItems().size();
+ if (p > 0) {
+ for (int i = 0; i < p; i++) {
+ final MenuItem potentialExactMatch = (MenuItem) getItems()
+ .get(i);
+ if (potentialExactMatch.getText().equals(enteredItemValue)) {
+ selectItem(potentialExactMatch);
+ doItemAction(potentialExactMatch, true);
+ suggestionPopup.hide();
+ return;
+ }
+ }
+ }
+ if (allowNewItem) {
+
+ if (!enteredItemValue.equals(emptyText)) {
+ client.updateVariable(paintableId, "newitem",
+ enteredItemValue, immediate);
+ }
+ } else if (item != null
+ && item.getText().toLowerCase().startsWith(
+ lastFilter.toLowerCase())) {
+ doItemAction(item, true);
+ } else {
+ if (currentSuggestion != null) {
+ String text = currentSuggestion.getReplacementString();
+ tb.setText((text.equals("") ? emptyText : text));
+ // TODO add/remove class CLASSNAME_EMPTY
+ selectedOptionKey = currentSuggestion.key;
+ } else {
+ tb.setText(emptyText);
+ // TODO add class CLASSNAME_EMPTY
+ selectedOptionKey = null;
+ }
+ }
+ suggestionPopup.hide();
+ }
+ }
+
+ public static final int FILTERINGMODE_OFF = 0;
+ public static final int FILTERINGMODE_STARTSWITH = 1;
+ public static final int FILTERINGMODE_CONTAINS = 2;
+
+ private static final String CLASSNAME = "i-filterselect";
+
+ public static final int PAGELENTH = 10;
+
+ private final FlowPanel panel = new FlowPanel();
+
+ private final TextBox tb = new TextBox() {
+ public void onBrowserEvent(Event event) {
+ super.onBrowserEvent(event);
+ if (client != null) {
+ client.handleTooltipEvent(event, IFilterSelect.this);
+ }
+ }
+ };
+
+ private final SuggestionPopup suggestionPopup = new SuggestionPopup();
+
+ private final HTML popupOpener = new HTML("");
+
+ private final Image selectedItemIcon = new Image();
+
+ private ApplicationConnection client;
+
+ private String paintableId;
+
+ private int currentPage;
+
+ private final Collection currentSuggestions = new ArrayList();
+
+ private boolean immediate;
+
+ private String selectedOptionKey;
+
+ private boolean filtering = false;
+
+ private String lastFilter = "";
+
+ private FilterSelectSuggestion currentSuggestion;
+
+ private int totalMatches;
+ private boolean allowNewItem;
+ private boolean nullSelectionAllowed;
+ private boolean enabled;
+
+ // shown in unfocused empty field, disappears on focus (e.g "Search here")
+ private String emptyText = "";
+ private static final String CLASSNAME_EMPTY = "empty";
+ private static final String ATTR_EMPTYTEXT = "emptytext";
+
+ public IFilterSelect() {
+ selectedItemIcon.setVisible(false);
+ panel.add(selectedItemIcon);
+ tb.sinkEvents(Tooltip.TOOLTIP_EVENTS);
+ panel.add(tb);
+ panel.add(popupOpener);
+ initWidget(panel);
+ setStyleName(CLASSNAME);
+ tb.addKeyboardListener(this);
+ tb.setStyleName(CLASSNAME + "-input");
+ tb.addFocusListener(this);
+ popupOpener.setStyleName(CLASSNAME + "-button");
+ popupOpener.addClickListener(this);
+ }
+
+ public boolean hasNextPage() {
+ if (totalMatches > (currentPage + 1) * PAGELENTH) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public void filterOptions(int page) {
+ filterOptions(page, tb.getText());
+ }
+
+ public void filterOptions(int page, String filter) {
+ if (filter.equals(lastFilter) && currentPage == page) {
+ if (!suggestionPopup.isAttached()) {
+ suggestionPopup.showSuggestions(currentSuggestions,
+ currentPage, totalMatches);
+ }
+ return;
+ }
+ if (!filter.equals(lastFilter)) {
+ // we are on subsequent page and text has changed -> reset page
+ if ("".equals(filter)) {
+ // let server decide
+ page = -1;
+ } else {
+ page = 0;
+ }
+ }
+
+ filtering = true;
+ client.updateVariable(paintableId, "filter", filter, false);
+ client.updateVariable(paintableId, "page", page, true);
+ lastFilter = filter;
+ currentPage = page;
+ }
+
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+ paintableId = uidl.getId();
+ this.client = client;
+
+ boolean readonly = uidl.hasAttribute("readonly");
+ boolean disabled = uidl.hasAttribute("disabled");
+
+ if (disabled || readonly) {
+ tb.setEnabled(false);
+ enabled = false;
+ } else {
+ tb.setEnabled(true);
+ enabled = true;
+ }
+
+ if (client.updateComponent(this, uidl, true)) {
+ return;
+ }
+
+ immediate = uidl.hasAttribute("immediate");
+
+ nullSelectionAllowed = uidl.hasAttribute("nullselect");
+
+ currentPage = uidl.getIntVariable("page");
+
+ if (uidl.hasAttribute(ATTR_EMPTYTEXT)) {
+ // "emptytext" changed from server
+ emptyText = uidl.getStringAttribute(ATTR_EMPTYTEXT);
+ }
+
+ suggestionPopup.setPagingEnabled(true);
+
+ allowNewItem = uidl.hasAttribute("allownewitem");
+
+ currentSuggestions.clear();
+ final UIDL options = uidl.getChildUIDL(0);
+ totalMatches = uidl.getIntAttribute("totalMatches");
+
+ String captions = emptyText;
+
+ for (final Iterator i = options.getChildIterator(); i.hasNext();) {
+ final UIDL optionUidl = (UIDL) i.next();
+ final FilterSelectSuggestion suggestion = new FilterSelectSuggestion(
+ optionUidl);
+ currentSuggestions.add(suggestion);
+ if (optionUidl.hasAttribute("selected")) {
+ if (!filtering) {
+ tb.setText(suggestion.getReplacementString());
+ }
+ currentSuggestion = suggestion;
+ }
+
+ // Collect captions so we can calculate minimum width for textarea
+ if (captions.length() > 0) {
+ captions += "|";
+ }
+ captions += suggestion.getReplacementString();
+ }
+
+ if (!filtering && uidl.hasVariable("selected")
+ && uidl.getStringArrayVariable("selected").length == 0) {
+ // select nulled
+ tb.setText(emptyText);
+ selectedOptionKey = null;
+ // TODO add class CLASSNAME_EMPTY
+ }
+
+ if (filtering
+ && lastFilter.toLowerCase().equals(
+ uidl.getStringVariable("filter"))) {
+ suggestionPopup.showSuggestions(currentSuggestions, currentPage,
+ totalMatches);
+ filtering = false;
+ }
+
+ // Calculate minumum textarea width
+ final int minw = minWidth(captions);
+ final Element spacer = DOM.createDiv();
+ DOM.setStyleAttribute(spacer, "width", minw + "px");
+ DOM.setStyleAttribute(spacer, "height", "0");
+ DOM.setStyleAttribute(spacer, "overflow", "hidden");
+ DOM.appendChild(panel.getElement(), spacer);
+
+ }
+
+ public void onSuggestionSelected(FilterSelectSuggestion suggestion) {
+ currentSuggestion = suggestion;
+ String newKey;
+ if (suggestion.key.equals("")) {
+ // "nullselection"
+ newKey = "";
+ } else {
+ // normal selection
+ newKey = String.valueOf(suggestion.getOptionKey());
+ }
+ String text = suggestion.getReplacementString();
+ tb.setText(text.equals("") ? emptyText : text);
+ // TODO add/remove class CLASSNAME_EMPTY
+ setSelectedItemIcon(suggestion.getIconUri());
+ if (!newKey.equals(selectedOptionKey)) {
+ selectedOptionKey = newKey;
+ client.updateVariable(paintableId, "selected",
+ new String[] { selectedOptionKey }, immediate);
+ // currentPage = -1; // forget the page
+ }
+ suggestionPopup.hide();
+ }
+
+ private void setSelectedItemIcon(String iconUri) {
+ if (iconUri == null) {
+ selectedItemIcon.setVisible(false);
+ } else {
+ selectedItemIcon.setUrl(iconUri);
+ selectedItemIcon.setVisible(true);
+ }
+ }
+
+ public void onKeyDown(Widget sender, char keyCode, int modifiers) {
+ if (enabled && suggestionPopup.isAttached()) {
+ switch (keyCode) {
+ case KeyboardListener.KEY_DOWN:
+ suggestionPopup.selectNextItem();
+ DOM.eventPreventDefault(DOM.eventGetCurrentEvent());
+ break;
+ case KeyboardListener.KEY_UP:
+ suggestionPopup.selectPrevItem();
+ DOM.eventPreventDefault(DOM.eventGetCurrentEvent());
+ break;
+ case KeyboardListener.KEY_PAGEDOWN:
+ if (hasNextPage()) {
+ filterOptions(currentPage + 1, lastFilter);
+ }
+ break;
+ case KeyboardListener.KEY_PAGEUP:
+ if (currentPage > 0) {
+ filterOptions(currentPage - 1, lastFilter);
+ }
+ break;
+ case KeyboardListener.KEY_ENTER:
+ case KeyboardListener.KEY_TAB:
+ suggestionPopup.menu.doSelectedItemAction();
+ break;
+ }
+ }
+ }
+
+ public void onKeyPress(Widget sender, char keyCode, int modifiers) {
+
+ }
+
+ public void onKeyUp(Widget sender, char keyCode, int modifiers) {
+ if (enabled) {
+ switch (keyCode) {
+ case KeyboardListener.KEY_ENTER:
+ case KeyboardListener.KEY_TAB:
+ ; // NOP
+ break;
+ case KeyboardListener.KEY_DOWN:
+ case KeyboardListener.KEY_UP:
+ case KeyboardListener.KEY_PAGEDOWN:
+ case KeyboardListener.KEY_PAGEUP:
+ if (suggestionPopup.isAttached()) {
+ break;
+ } else {
+ // open popup as from gadget
+ filterOptions(-1, "");
+ lastFilter = "";
+ tb.selectAll();
+ break;
+ }
+ default:
+ filterOptions(currentPage);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Listener for popupopener
+ */
+ public void onClick(Widget sender) {
+ if (enabled) {
+ // ask suggestionPopup if it was just closed, we are using GWT
+ // Popup's
+ // auto close feature
+ if (!suggestionPopup.isJustClosed()) {
+ filterOptions(-1, "");
+ lastFilter = "";
+ }
+ DOM.eventPreventDefault(DOM.eventGetCurrentEvent());
+ tb.setFocus(true);
+ tb.selectAll();
+
+ }
+ }
+
+ /*
+ * Calculate minumum width for FilterSelect textarea
+ */
+ private native int minWidth(String captions)
+ /*-{
+ if(!captions || captions.length <= 0)
+ return 0;
+ captions = captions.split("|");
+ var d = $wnd.document.createElement("div");
+ var html = "";
+ for(var i=0; i < captions.length; i++) {
+ html += "<div>" + captions[i] + "</div>";
+ // TODO apply same CSS classname as in suggestionmenu
+ }
+ d.style.position = "absolute";
+ d.style.top = "0";
+ d.style.left = "0";
+ d.style.visibility = "hidden";
+ d.innerHTML = html;
+ $wnd.document.body.appendChild(d);
+ var w = d.offsetWidth;
+ $wnd.document.body.removeChild(d);
+ return w;
+ }-*/;
+
+ public void onFocus(Widget sender) {
+ if (emptyText.equals(tb.getText())) {
+ tb.setText("");
+ // TODO remove class CLASSNAME_EMPTY
+ }
+ }
+
+ public void onLostFocus(Widget sender) {
+ if (suggestionPopup.isJustClosed()) {
+ suggestionPopup.menu.doSelectedItemAction();
+ }
+ if ("".equals(tb.getText())) {
+ tb.setText(emptyText);
+ // TODO add CLASSNAME_EMPTY
+ }
+ }
+
+ public void focus() {
+ tb.setFocus(true);
+ }
+}
--- /dev/null
+/* \r
+@ITMillApache2LicenseForJavaFiles@\r
+ */\r
+\r
+package com.itmill.toolkit.terminal.gwt.client.ui;\r
+\r
+import com.google.gwt.user.client.DOM;\r
+import com.google.gwt.user.client.Element;\r
+import com.google.gwt.user.client.ui.ComplexPanel;\r
+import com.google.gwt.user.client.ui.Widget;\r
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;\r
+import com.itmill.toolkit.terminal.gwt.client.Container;\r
+import com.itmill.toolkit.terminal.gwt.client.ContainerResizedListener;\r
+import com.itmill.toolkit.terminal.gwt.client.ErrorMessage;\r
+import com.itmill.toolkit.terminal.gwt.client.Paintable;\r
+import com.itmill.toolkit.terminal.gwt.client.UIDL;\r
+import com.itmill.toolkit.terminal.gwt.client.Util;\r
+\r
+public class IForm extends ComplexPanel implements Paintable,\r
+ ContainerResizedListener {\r
+\r
+ public static final String CLASSNAME = "i-form";\r
+\r
+ private Container lo;\r
+ private Element legend = DOM.createLegend();\r
+ private Element caption = DOM.createSpan();\r
+ private Element errorIndicatorElement = DOM.createDiv();\r
+ private Element desc = DOM.createDiv();\r
+ private Icon icon;\r
+ private ErrorMessage errorMessage = new ErrorMessage();\r
+\r
+ private Element fieldContainer = DOM.createDiv();\r
+\r
+ private Element footerContainer = DOM.createDiv();\r
+\r
+ private Container footer;\r
+\r
+ public IForm() {\r
+ setElement(DOM.createFieldSet());\r
+ setStyleName(CLASSNAME);\r
+ DOM.appendChild(getElement(), legend);\r
+ DOM.appendChild(legend, caption);\r
+ DOM.setElementProperty(errorIndicatorElement, "className",\r
+ "i-errorindicator");\r
+ DOM.setStyleAttribute(errorIndicatorElement, "display", "none");\r
+ DOM.setInnerText(errorIndicatorElement, " "); // needed for IE\r
+ DOM.setElementProperty(desc, "className", "i-form-description");\r
+ DOM.appendChild(getElement(), desc);\r
+ DOM.appendChild(getElement(), fieldContainer);\r
+ errorMessage.setVisible(false);\r
+ errorMessage.setStyleName(CLASSNAME + "-errormessage");\r
+ DOM.appendChild(getElement(), errorMessage.getElement());\r
+ DOM.appendChild(getElement(), footerContainer);\r
+ }\r
+\r
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {\r
+ if (client.updateComponent(this, uidl, false)) {\r
+ return;\r
+ }\r
+\r
+ boolean legendEmpty = true;\r
+ if (uidl.hasAttribute("caption")) {\r
+ DOM.setInnerText(caption, uidl.getStringAttribute("caption"));\r
+ legendEmpty = false;\r
+ } else {\r
+ DOM.setInnerText(caption, "");\r
+ }\r
+ if (uidl.hasAttribute("icon")) {\r
+ if (icon == null) {\r
+ icon = new Icon(client);\r
+ DOM.insertChild(legend, icon.getElement(), 0);\r
+ }\r
+ icon.setUri(uidl.getStringAttribute("icon"));\r
+ legendEmpty = false;\r
+ } else {\r
+ if (icon != null) {\r
+ DOM.removeChild(legend, icon.getElement());\r
+ }\r
+ }\r
+ if (legendEmpty) {\r
+ DOM.setStyleAttribute(legend, "display", "none");\r
+ } else {\r
+ DOM.setStyleAttribute(legend, "display", "");\r
+ }\r
+\r
+ if (uidl.hasAttribute("error")) {\r
+ final UIDL errorUidl = uidl.getErrors();\r
+ errorMessage.updateFromUIDL(errorUidl);\r
+ errorMessage.setVisible(true);\r
+\r
+ } else {\r
+ errorMessage.setVisible(false);\r
+ }\r
+\r
+ if (uidl.hasAttribute("description")) {\r
+ DOM.setInnerHTML(desc, uidl.getStringAttribute("description"));\r
+ } else {\r
+ DOM.setInnerHTML(desc, "");\r
+ }\r
+\r
+ iLayout();\r
+\r
+ final UIDL layoutUidl = uidl.getChildUIDL(0);\r
+ Container newLo = (Container) client.getPaintable(layoutUidl);\r
+ if (lo == null) {\r
+ lo = newLo;\r
+ add((Widget) lo, fieldContainer);\r
+ } else if (lo != newLo) {\r
+ client.unregisterPaintable(lo);\r
+ remove((Widget) lo);\r
+ lo = newLo;\r
+ add((Widget) lo, fieldContainer);\r
+ }\r
+ lo.updateFromUIDL(layoutUidl, client);\r
+\r
+ if (uidl.getChildCount() > 1) {\r
+ // render footer\r
+ Container newFooter = (Container) client.getPaintable(uidl\r
+ .getChildUIDL(1));\r
+ if (footer == null) {\r
+ add((Widget) newFooter, footerContainer);\r
+ footer = newFooter;\r
+ } else if (newFooter != footer) {\r
+ remove((Widget) footer);\r
+ client.unregisterPaintable(footer);\r
+ add((Widget) newFooter, footerContainer);\r
+ }\r
+ footer = newFooter;\r
+ footer.updateFromUIDL(uidl.getChildUIDL(1), client);\r
+ } else {\r
+ if (footer != null) {\r
+ remove((Widget) footer);\r
+ client.unregisterPaintable(footer);\r
+ }\r
+ }\r
+ }\r
+\r
+ public void iLayout() {\r
+ // fix contained components container size as they may have relative\r
+ // widths\r
+ DOM.setStyleAttribute(fieldContainer, "width", "");\r
+ DOM.setStyleAttribute(footerContainer, "width", "");\r
+ int width = DOM.getElementPropertyInt(desc, "offsetWidth");\r
+ DOM.setStyleAttribute(fieldContainer, "width", width + "px");\r
+ DOM.setStyleAttribute(footerContainer, "width", width + "px");\r
+ Util.runDescendentsLayout(this);\r
+ }\r
+\r
+}\r
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client.ui;
+
+import java.util.HashMap;
+import java.util.Iterator;
+
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.FlexTable;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.Widget;
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;
+import com.itmill.toolkit.terminal.gwt.client.Container;
+import com.itmill.toolkit.terminal.gwt.client.Paintable;
+import com.itmill.toolkit.terminal.gwt.client.StyleConstants;
+import com.itmill.toolkit.terminal.gwt.client.UIDL;
+import com.itmill.toolkit.terminal.gwt.client.Util;
+
+/**
+ * Two col Layout that places caption on left col and field on right col
+ */
+public class IFormLayout extends FlexTable implements Container {
+
+ private final static String CLASSNAME = "i-formlayout";
+
+ HashMap componentToCaption = new HashMap();
+ private ApplicationConnection client;
+ private HashMap componentToError = new HashMap();
+
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+ this.client = client;
+
+ if (client.updateComponent(this, uidl, false)) {
+ return;
+ }
+
+ final MarginInfo margins = new MarginInfo(uidl
+ .getIntAttribute("margins"));
+
+ Element margin = getElement();
+ setStyleName(margin, CLASSNAME + "-" + StyleConstants.MARGIN_TOP,
+ margins.hasTop());
+ setStyleName(margin, CLASSNAME + "-" + StyleConstants.MARGIN_RIGHT,
+ margins.hasRight());
+ setStyleName(margin, CLASSNAME + "-" + StyleConstants.MARGIN_BOTTOM,
+ margins.hasBottom());
+ setStyleName(margin, CLASSNAME + "-" + StyleConstants.MARGIN_LEFT,
+ margins.hasLeft());
+
+ setStyleName(margin, CLASSNAME + "-" + "spacing", uidl
+ .hasAttribute("spacing"));
+
+ int i = 0;
+ for (final Iterator it = uidl.getChildIterator(); it.hasNext(); i++) {
+ prepareCell(i, 1);
+ final UIDL childUidl = (UIDL) it.next();
+ final Paintable p = client.getPaintable(childUidl);
+ Caption caption = (Caption) componentToCaption.get(p);
+ if (caption == null) {
+ caption = new Caption(p, client);
+ componentToCaption.put(p, caption);
+ }
+ ErrorFlag error = (ErrorFlag) componentToError.get(p);
+ if (error == null) {
+ error = new ErrorFlag();
+ componentToError.put(p, error);
+ }
+ prepareCell(i, 2);
+ final Paintable oldComponent = (Paintable) getWidget(i, 2);
+ if (oldComponent == null) {
+ setWidget(i, 2, (Widget) p);
+ } else if (oldComponent != p) {
+ client.unregisterPaintable(oldComponent);
+ setWidget(i, 2, (Widget) p);
+ }
+ getCellFormatter().setStyleName(i, 2, CLASSNAME + "-contentcell");
+ getCellFormatter().setStyleName(i, 0, CLASSNAME + "-captioncell");
+ setWidget(i, 0, caption);
+
+ getCellFormatter().setStyleName(i, 1, CLASSNAME + "-errorcell");
+ setWidget(i, 1, error);
+
+ p.updateFromUIDL(childUidl, client);
+
+ String rowstyles = CLASSNAME + "-row";
+ if (i == 0) {
+ rowstyles += " " + CLASSNAME + "-firstrow";
+ }
+ if (!it.hasNext()) {
+ rowstyles += " " + CLASSNAME + "-lastrow";
+ }
+
+ getRowFormatter().setStyleName(i, rowstyles);
+
+ }
+
+ while (getRowCount() > i) {
+ final Paintable p = (Paintable) getWidget(i, 2);
+ client.unregisterPaintable(p);
+ componentToCaption.remove(p);
+ removeRow(i);
+ }
+ }
+
+ public boolean hasChildComponent(Widget component) {
+ return componentToCaption.containsKey(component);
+ }
+
+ public void replaceChildComponent(Widget oldComponent, Widget newComponent) {
+ int i;
+ for (i = 0; i < getRowCount(); i++) {
+ if (oldComponent == getWidget(i, 1)) {
+ final Caption newCap = new Caption((Paintable) newComponent,
+ client);
+ setWidget(i, 0, newCap);
+ setWidget(i, 1, newComponent);
+ client.unregisterPaintable((Paintable) oldComponent);
+ break;
+ }
+ }
+ }
+
+ public void updateCaption(Paintable component, UIDL uidl) {
+ final Caption c = (Caption) componentToCaption.get(component);
+ if (c != null) {
+ c.updateCaption(uidl);
+ }
+ final ErrorFlag e = (ErrorFlag) componentToError.get(component);
+ if (e != null) {
+ e.updateFromUIDL(uidl, component);
+ }
+ }
+
+ public class Caption extends HTML {
+
+ public static final String CLASSNAME = "i-caption";
+
+ private final Paintable owner;
+
+ private Element requiredFieldIndicator;
+
+ private Icon icon;
+
+ private Element captionText;
+
+ private final ApplicationConnection client;
+
+ /**
+ *
+ * @param component
+ * optional owner of caption. If not set, getOwner will
+ * return null
+ * @param client
+ */
+ public Caption(Paintable component, ApplicationConnection client) {
+ super();
+ this.client = client;
+ owner = component;
+ setStyleName(CLASSNAME);
+ }
+
+ public void updateCaption(UIDL uidl) {
+ setVisible(!uidl.getBooleanAttribute("invisible"));
+
+ setStyleName(getElement(), "i-disabled", uidl
+ .hasAttribute("disabled"));
+
+ boolean isEmpty = true;
+
+ if (uidl.hasAttribute("icon")) {
+ if (icon == null) {
+ icon = new Icon(client);
+
+ DOM.insertChild(getElement(), icon.getElement(), 0);
+ }
+ icon.setUri(uidl.getStringAttribute("icon"));
+ isEmpty = false;
+ } else {
+ if (icon != null) {
+ DOM.removeChild(getElement(), icon.getElement());
+ icon = null;
+ }
+
+ }
+
+ if (uidl.hasAttribute("caption")) {
+ if (captionText == null) {
+ captionText = DOM.createSpan();
+ DOM.insertChild(getElement(), captionText, icon == null ? 0
+ : 1);
+ }
+ String c = uidl.getStringAttribute("caption");
+ if (c == null) {
+ c = "";
+ } else {
+ isEmpty = false;
+ }
+ DOM.setInnerText(captionText, c);
+ } else {
+ // TODO should span also be removed
+ }
+
+ if (uidl.hasAttribute("description")) {
+ if (captionText != null) {
+ addStyleDependentName("hasdescription");
+ } else {
+ removeStyleDependentName("hasdescription");
+ }
+ }
+
+ if (uidl.getBooleanAttribute("required")) {
+ if (requiredFieldIndicator == null) {
+ requiredFieldIndicator = DOM.createSpan();
+ DOM.setInnerText(requiredFieldIndicator, "*");
+ DOM.setElementProperty(requiredFieldIndicator, "className",
+ "i-required-field-indicator");
+ DOM.appendChild(getElement(), requiredFieldIndicator);
+ }
+ } else {
+ if (requiredFieldIndicator != null) {
+ DOM.removeChild(getElement(), requiredFieldIndicator);
+ requiredFieldIndicator = null;
+ }
+ }
+
+ // Workaround for IE weirdness, sometimes returns bad height in some
+ // circumstances when Caption is empty. See #1444
+ // IE7 bugs more often. I wonder what happens when IE8 arrives...
+ if (Util.isIE()) {
+ if (isEmpty) {
+ setHeight("0px");
+ DOM.setStyleAttribute(getElement(), "overflow", "hidden");
+ } else {
+ setHeight("");
+ DOM.setStyleAttribute(getElement(), "overflow", "");
+ }
+
+ }
+
+ }
+
+ /**
+ * Returns Paintable for which this Caption belongs to.
+ *
+ * @return owner Widget
+ */
+ public Paintable getOwner() {
+ return owner;
+ }
+
+ public void onBrowserEvent(Event event) {
+ super.onBrowserEvent(event);
+ if (client != null) {
+ client.handleTooltipEvent(event, owner);
+ }
+ }
+ }
+
+ private class ErrorFlag extends HTML {
+ private static final String CLASSNAME = ".i-form-layout-error-indicator";
+ Element errorIndicatorElement;
+ private Paintable owner;
+
+ public ErrorFlag() {
+ setStyleName(CLASSNAME);
+ }
+
+ public void updateFromUIDL(UIDL uidl, Paintable component) {
+ owner = component;
+ if (uidl.hasAttribute("error")) {
+ if (errorIndicatorElement == null) {
+ errorIndicatorElement = DOM.createDiv();
+ if (Util.isIE()) {
+ DOM.setInnerHTML(errorIndicatorElement, " ");
+ }
+ DOM.setElementProperty(errorIndicatorElement, "className",
+ "i-errorindicator");
+ DOM.appendChild(getElement(), errorIndicatorElement);
+ }
+
+ } else if (errorIndicatorElement != null) {
+ DOM.removeChild(getElement(), errorIndicatorElement);
+ errorIndicatorElement = null;
+ }
+ }
+
+ public void onBrowserEvent(Event event) {
+ super.onBrowserEvent(event);
+ if (owner != null) {
+ client.handleTooltipEvent(event, owner);
+ }
+ }
+
+ }
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client.ui;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.DeferredCommand;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.ui.FlexTable;
+import com.google.gwt.user.client.ui.HasHorizontalAlignment;
+import com.google.gwt.user.client.ui.HasVerticalAlignment;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.gwt.user.client.ui.HasHorizontalAlignment.HorizontalAlignmentConstant;
+import com.google.gwt.user.client.ui.HasVerticalAlignment.VerticalAlignmentConstant;
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;
+import com.itmill.toolkit.terminal.gwt.client.BrowserInfo;
+import com.itmill.toolkit.terminal.gwt.client.CaptionWrapper;
+import com.itmill.toolkit.terminal.gwt.client.Container;
+import com.itmill.toolkit.terminal.gwt.client.ContainerResizedListener;
+import com.itmill.toolkit.terminal.gwt.client.Paintable;
+import com.itmill.toolkit.terminal.gwt.client.StyleConstants;
+import com.itmill.toolkit.terminal.gwt.client.UIDL;
+import com.itmill.toolkit.terminal.gwt.client.Util;
+
+public class IGridLayout extends SimplePanel implements Paintable, Container,
+ ContainerResizedListener {
+
+ public static final String CLASSNAME = "i-gridlayout";
+
+ private Grid grid = new Grid();
+
+ private boolean needsLayout = false;
+
+ private boolean needsFF2Hack = BrowserInfo.get().isFF2();
+
+ private Element margin = DOM.createDiv();
+
+ private Element meterElement;
+
+ private String width;
+
+ public IGridLayout() {
+ super();
+ DOM.appendChild(getElement(), margin);
+ DOM.setStyleAttribute(getElement(), "overflow", "hidden");
+ setStyleName(CLASSNAME);
+ setWidget(grid);
+ }
+
+ protected Element getContainerElement() {
+ return margin;
+ }
+
+ public void setWidth(String width) {
+ this.width = width;
+ if (width != null && !width.equals("")) {
+ needsLayout = true;
+ } else {
+ needsLayout = false;
+ grid.setWidth("");
+ }
+ }
+
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+
+ if (client.updateComponent(this, uidl, false)) {
+ return;
+ }
+ final MarginInfo margins = new MarginInfo(uidl
+ .getIntAttribute("margins"));
+
+ setStyleName(margin, CLASSNAME + "-" + StyleConstants.MARGIN_TOP,
+ margins.hasTop());
+ setStyleName(margin, CLASSNAME + "-" + StyleConstants.MARGIN_RIGHT,
+ margins.hasRight());
+ setStyleName(margin, CLASSNAME + "-" + StyleConstants.MARGIN_BOTTOM,
+ margins.hasBottom());
+ setStyleName(margin, CLASSNAME + "-" + StyleConstants.MARGIN_LEFT,
+ margins.hasLeft());
+
+ setStyleName(margin, CLASSNAME + "-" + "spacing", uidl
+ .hasAttribute("spacing"));
+ iLayout();
+ grid.updateFromUIDL(uidl, client);
+ }
+
+ public boolean hasChildComponent(Widget component) {
+ return grid.hasChildComponent(component);
+ }
+
+ public void replaceChildComponent(Widget oldComponent, Widget newComponent) {
+ grid.replaceChildComponent(oldComponent, newComponent);
+ }
+
+ public void updateCaption(Paintable component, UIDL uidl) {
+ grid.updateCaption(component, uidl);
+ }
+
+ public class Grid extends FlexTable implements Paintable, Container {
+
+ /** Widget to captionwrapper map */
+ private final HashMap widgetToCaptionWrapper = new HashMap();
+
+ public Grid() {
+ super();
+ setStyleName(CLASSNAME + "-grid");
+ }
+
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+
+ int row = 0, column = 0;
+
+ final ArrayList oldWidgetWrappers = new ArrayList();
+ for (final Iterator iterator = iterator(); iterator.hasNext();) {
+ oldWidgetWrappers.add(iterator.next());
+ }
+ clear();
+
+ final int[] alignments = uidl.getIntArrayAttribute("alignments");
+ int alignmentIndex = 0;
+
+ for (final Iterator i = uidl.getChildIterator(); i.hasNext();) {
+ final UIDL r = (UIDL) i.next();
+ if ("gr".equals(r.getTag())) {
+ column = 0;
+ for (final Iterator j = r.getChildIterator(); j.hasNext();) {
+ final UIDL c = (UIDL) j.next();
+ if ("gc".equals(c.getTag())) {
+ prepareCell(row, column);
+
+ // Set cell width
+ int w;
+ if (c.hasAttribute("w")) {
+ w = c.getIntAttribute("w");
+ } else {
+ w = 1;
+ }
+
+ FlexCellFormatter formatter = (FlexCellFormatter) getCellFormatter();
+
+ // set col span
+ formatter.setColSpan(row, column, w);
+
+ String styleNames = CLASSNAME + "-cell";
+ if (column == 0) {
+ styleNames += " " + CLASSNAME + "-firstcol";
+ }
+ if (row == 0) {
+ styleNames += " " + CLASSNAME + "-firstrow";
+ }
+ formatter.setStyleName(row, column, styleNames);
+
+ // Set cell height
+ int h;
+ if (c.hasAttribute("h")) {
+ h = c.getIntAttribute("h");
+ } else {
+ h = 1;
+ }
+ ((FlexCellFormatter) getCellFormatter())
+ .setRowSpan(row, column, h);
+
+ final UIDL u = c.getChildUIDL(0);
+ if (u != null) {
+
+ AlignmentInfo alignmentInfo = new AlignmentInfo(
+ alignments[alignmentIndex++]);
+
+ VerticalAlignmentConstant va;
+ if (alignmentInfo.isBottom()) {
+ va = HasVerticalAlignment.ALIGN_BOTTOM;
+ } else if (alignmentInfo.isTop()) {
+ va = HasVerticalAlignment.ALIGN_TOP;
+ } else {
+ va = HasVerticalAlignment.ALIGN_MIDDLE;
+ }
+
+ HorizontalAlignmentConstant ha;
+
+ if (alignmentInfo.isLeft()) {
+ ha = HasHorizontalAlignment.ALIGN_LEFT;
+ } else if (alignmentInfo.isHorizontalCenter()) {
+ ha = HasHorizontalAlignment.ALIGN_CENTER;
+ } else {
+ ha = HasHorizontalAlignment.ALIGN_RIGHT;
+ }
+
+ formatter.setAlignment(row, column, ha, va);
+
+ final Paintable child = client.getPaintable(u);
+ CaptionWrapper wr;
+ if (widgetToCaptionWrapper.containsKey(child)) {
+ wr = (CaptionWrapper) widgetToCaptionWrapper
+ .get(child);
+ oldWidgetWrappers.remove(wr);
+ } else {
+ wr = new CaptionWrapper(child, client);
+ widgetToCaptionWrapper.put(child, wr);
+ }
+
+ setWidget(row, column, wr);
+
+ DOM.setStyleAttribute(wr.getElement(),
+ "textAlign", alignmentInfo
+ .getHorizontalAlignment());
+
+ if (!u.getBooleanAttribute("cached")) {
+ child.updateFromUIDL(u, client);
+ }
+ }
+ column += w;
+ }
+ }
+ row++;
+ }
+ }
+
+ // loop oldWidgetWrappers that where not re-attached and unregister
+ // them
+ for (final Iterator it = oldWidgetWrappers.iterator(); it.hasNext();) {
+ final CaptionWrapper w = (CaptionWrapper) it.next();
+ client.unregisterPaintable(w.getPaintable());
+ widgetToCaptionWrapper.remove(w.getPaintable());
+ }
+ // fix rendering bug on FF2 (#1838)
+ if (needsFF2Hack) {
+ DeferredCommand.addCommand(new Command() {
+ public void execute() {
+ Element firstcell = getCellFormatter().getElement(0, 0);
+ if (firstcell != null) {
+ String styleAttribute = DOM.getStyleAttribute(
+ firstcell, "verticalAlign");
+ DOM.setStyleAttribute(firstcell, "verticalAlign",
+ "");
+ int elementPropertyInt = DOM.getElementPropertyInt(
+ firstcell, "offsetWidth");
+ DOM.setStyleAttribute(firstcell, "verticalAlign",
+ styleAttribute);
+ if (elementPropertyInt > 0) {
+ needsFF2Hack = false;
+ }
+ }
+ }
+ });
+ }
+ }
+
+ public boolean hasChildComponent(Widget component) {
+ if (widgetToCaptionWrapper.containsKey(component)) {
+ return true;
+ }
+ return false;
+ }
+
+ public void replaceChildComponent(Widget oldComponent,
+ Widget newComponent) {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void updateCaption(Paintable component, UIDL uidl) {
+ final CaptionWrapper wrapper = (CaptionWrapper) widgetToCaptionWrapper
+ .get(component);
+ wrapper.updateCaption(uidl);
+ }
+
+ }
+
+ public void iLayout() {
+ if (needsLayout) {
+ super.setWidth(width);
+ if (meterElement == null) {
+ meterElement = DOM.createDiv();
+ DOM.setStyleAttribute(meterElement, "overflow", "hidden");
+ DOM.setStyleAttribute(meterElement, "height", "0");
+ DOM.appendChild(getContainerElement(), meterElement);
+ }
+ int contentWidth = DOM.getElementPropertyInt(meterElement,
+ "offsetWidth");
+ int offsetWidth = getOffsetWidth();
+
+ grid.setWidth((offsetWidth - (offsetWidth - contentWidth)) + "px");
+ } else {
+ grid.setWidth("");
+ }
+ Util.runDescendentsLayout(this);
+ }
+
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client.ui;
+
+public class IHorizontalExpandLayout extends IExpandLayout {
+
+ public IHorizontalExpandLayout() {
+ super(IExpandLayout.ORIENTATION_HORIZONTAL);
+ }
+
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client.ui;
+
+import com.google.gwt.user.client.ui.HTML;
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;
+import com.itmill.toolkit.terminal.gwt.client.Paintable;
+import com.itmill.toolkit.terminal.gwt.client.UIDL;
+
+public class ILabel extends HTML implements Paintable {
+
+ public static final String CLASSNAME = "i-label";
+
+ public ILabel() {
+ super();
+ setStyleName(CLASSNAME);
+ }
+
+ public ILabel(String text) {
+ super(text);
+ setStyleName(CLASSNAME);
+ }
+
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+
+ if (client.updateComponent(this, uidl, true)) {
+ return;
+ }
+
+ final String mode = uidl.getStringAttribute("mode");
+ if (mode == null || "text".equals(mode)) {
+ setText(uidl.getChildString(0));
+ } else if ("pre".equals(mode)) {
+ setHTML(uidl.getChildrenAsXML());
+ } else if ("uidl".equals(mode)) {
+ setHTML(uidl.getChildrenAsXML());
+ } else if ("xhtml".equals(mode)) {
+ setHTML(uidl.getChildUIDL(0).getChildUIDL(0).getChildString(0));
+ } else if ("xml".equals(mode)) {
+ setHTML(uidl.getChildUIDL(0).getChildString(0));
+ } else if ("raw".equals(mode)) {
+ setHTML(uidl.getChildUIDL(0).getChildString(0));
+ } else {
+ setText("");
+ }
+ }
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client.ui;
+
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.ClickListener;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.Widget;
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;
+import com.itmill.toolkit.terminal.gwt.client.ErrorMessage;
+import com.itmill.toolkit.terminal.gwt.client.Paintable;
+import com.itmill.toolkit.terminal.gwt.client.UIDL;
+
+public class ILink extends HTML implements Paintable, ClickListener {
+
+ public static final String CLASSNAME = "i-link";
+
+ private static final int BORDER_STYLE_DEFAULT = 0;
+ private static final int BORDER_STYLE_MINIMAL = 1;
+ private static final int BORDER_STYLE_NONE = 2;
+
+ private String src;
+
+ private String target;
+
+ private int borderStyle = BORDER_STYLE_DEFAULT;
+
+ private boolean enabled;
+
+ private boolean readonly;
+
+ private int targetWidth;
+
+ private int targetHeight;
+
+ private Element errorIndicatorElement;
+
+ private final Element captionElement = DOM.createSpan();
+
+ private ErrorMessage errorMessage;
+
+ private Icon icon;
+
+ public ILink() {
+ super();
+ DOM.appendChild(getElement(), captionElement);
+ addClickListener(this);
+ setStyleName(CLASSNAME);
+ }
+
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+
+ // Ensure correct implementation,
+ // but don't let container manage caption etc.
+ if (client.updateComponent(this, uidl, false)) {
+ return;
+ }
+
+ enabled = uidl.hasAttribute("disabled") ? false : true;
+ readonly = uidl.hasAttribute("readonly") ? true : false;
+
+ if (uidl.hasAttribute("name")) {
+ target = uidl.getStringAttribute("name");
+ }
+ if (uidl.hasAttribute("src")) {
+ src = client.translateToolkitUri(uidl.getStringAttribute("src"));
+ }
+
+ if (uidl.hasAttribute("border")) {
+ if ("none".equals(uidl.getStringAttribute("border"))) {
+ borderStyle = BORDER_STYLE_NONE;
+ } else {
+ borderStyle = BORDER_STYLE_MINIMAL;
+ }
+ } else {
+ borderStyle = BORDER_STYLE_DEFAULT;
+ }
+
+ targetHeight = uidl.hasAttribute("height") ? uidl
+ .getIntAttribute("targetHeight") : -1;
+ targetWidth = uidl.hasAttribute("width") ? uidl
+ .getIntAttribute("targetHeidth") : -1;
+
+ // Set link caption
+ DOM.setInnerText(captionElement, uidl.getStringAttribute("caption"));
+
+ // handle error
+ if (uidl.hasAttribute("error")) {
+ final UIDL errorUidl = uidl.getErrors();
+ if (errorIndicatorElement == null) {
+ errorIndicatorElement = DOM.createDiv();
+ DOM.setElementProperty(errorIndicatorElement, "className",
+ "i-errorindicator");
+ DOM.sinkEvents(errorIndicatorElement, Event.MOUSEEVENTS);
+ sinkEvents(Event.MOUSEEVENTS);
+ }
+ DOM.insertChild(getElement(), errorIndicatorElement, 0);
+ if (errorMessage == null) {
+ errorMessage = new ErrorMessage();
+ }
+ errorMessage.updateFromUIDL(errorUidl);
+
+ } else if (errorIndicatorElement != null) {
+ DOM.setStyleAttribute(errorIndicatorElement, "display", "none");
+ }
+
+ if (uidl.hasAttribute("icon")) {
+ if (icon == null) {
+ icon = new Icon(client);
+ DOM.insertChild(getElement(), icon.getElement(), 0);
+ }
+ icon.setUri(uidl.getStringAttribute("icon"));
+ }
+
+ // handle description
+ if (uidl.hasAttribute("description")) {
+ setTitle(uidl.getStringAttribute("description"));
+ }
+
+ }
+
+ public void onClick(Widget sender) {
+ if (enabled && !readonly) {
+ if (target == null) {
+ target = "_self";
+ }
+ String features;
+ switch (borderStyle) {
+ case BORDER_STYLE_NONE:
+ features = "menubar=no,location=no,status=no";
+ break;
+ case BORDER_STYLE_MINIMAL:
+ features = "menubar=yes,location=no,status=no";
+ break;
+ default:
+ features = "";
+ break;
+ }
+
+ if (targetWidth > 0) {
+ features += (features.length() > 0 ? "," : "") + "width="
+ + targetWidth;
+ }
+ if (targetHeight > 0) {
+ features += (features.length() > 0 ? "," : "") + "height="
+ + targetHeight;
+ }
+
+ Window.open(src, target, features);
+ }
+ }
+
+ public void onBrowserEvent(Event event) {
+ final Element target = DOM.eventGetTarget(event);
+ if (errorIndicatorElement != null
+ && DOM.compare(target, errorIndicatorElement)) {
+ switch (DOM.eventGetType(event)) {
+ case Event.ONMOUSEOVER:
+ showErrorMessage();
+ break;
+ case Event.ONMOUSEOUT:
+ hideErrorMessage();
+ break;
+ case Event.ONCLICK:
+ ApplicationConnection.getConsole().log(
+ DOM.getInnerHTML(errorMessage.getElement()));
+ return;
+ default:
+ break;
+ }
+ }
+ if (DOM.compare(target, captionElement)
+ || (icon != null && DOM.compare(target, icon.getElement()))) {
+ super.onBrowserEvent(event);
+ }
+ }
+
+ private void hideErrorMessage() {
+ errorMessage.hide();
+ }
+
+ private void showErrorMessage() {
+ if (errorMessage != null) {
+ errorMessage.showAt(errorIndicatorElement);
+ }
+ }
+
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client.ui;
+
+import java.util.Iterator;
+import java.util.Vector;
+
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.ListBox;
+import com.google.gwt.user.client.ui.Widget;
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;
+import com.itmill.toolkit.terminal.gwt.client.Paintable;
+import com.itmill.toolkit.terminal.gwt.client.Tooltip;
+import com.itmill.toolkit.terminal.gwt.client.UIDL;
+
+public class IListSelect extends IOptionGroupBase {
+
+ public static final String CLASSNAME = "i-select";
+
+ private static final int VISIBLE_COUNT = 10;
+
+ protected TooltipListBox select;
+
+ private int lastSelectedIndex = -1;
+
+ public IListSelect() {
+ super(new TooltipListBox(true), CLASSNAME);
+ select = (TooltipListBox) optionsContainer;
+ select.setSelect(this);
+ select.addChangeListener(this);
+ select.addClickListener(this);
+ select.setStyleName(CLASSNAME + "-select");
+ select.setVisibleItemCount(VISIBLE_COUNT);
+ }
+
+ protected void buildOptions(UIDL uidl) {
+ select.setClient(client);
+ select.setMultipleSelect(isMultiselect());
+ select.setEnabled(!isDisabled() && !isReadonly());
+ select.clear();
+ if (!isMultiselect() && isNullSelectionAllowed()
+ && !isNullSelectionItemAvailable()) {
+ // can't unselect last item in singleselect mode
+ select.addItem("", null);
+ }
+ for (final Iterator i = uidl.getChildIterator(); i.hasNext();) {
+ final UIDL optionUidl = (UIDL) i.next();
+ select.addItem(optionUidl.getStringAttribute("caption"), optionUidl
+ .getStringAttribute("key"));
+ if (optionUidl.hasAttribute("selected")) {
+ select.setItemSelected(select.getItemCount() - 1, true);
+ }
+ }
+ if (getRows() > 0) {
+ select.setVisibleItemCount(getRows());
+ }
+ }
+
+ protected Object[] getSelectedItems() {
+ final Vector selectedItemKeys = new Vector();
+ for (int i = 0; i < select.getItemCount(); i++) {
+ if (select.isItemSelected(i)) {
+ selectedItemKeys.add(select.getValue(i));
+ }
+ }
+ return selectedItemKeys.toArray();
+ }
+
+ public void onChange(Widget sender) {
+ final int si = select.getSelectedIndex();
+ if (si == -1 && !isNullSelectionAllowed()) {
+ select.setSelectedIndex(lastSelectedIndex);
+ } else {
+ lastSelectedIndex = si;
+ if (isMultiselect()) {
+ client.updateVariable(id, "selected", getSelectedItems(),
+ isImmediate());
+ } else {
+ client.updateVariable(id, "selected", new String[] { ""
+ + getSelectedItem() }, isImmediate());
+ }
+ }
+ }
+
+ public void setHeight(String height) {
+ select.setHeight(height);
+ super.setHeight(height);
+ }
+
+ public void setWidth(String width) {
+ select.setWidth(width);
+ super.setWidth(width);
+ }
+
+}
+
+/**
+ * Extended ListBox to listen tooltip events and forward them to generic
+ * handler.
+ */
+class TooltipListBox extends ListBox {
+ private ApplicationConnection client;
+ private Paintable pntbl;
+
+ TooltipListBox(boolean isMultiselect) {
+ super(isMultiselect);
+ sinkEvents(Tooltip.TOOLTIP_EVENTS);
+ }
+
+ public void setClient(ApplicationConnection client) {
+ this.client = client;
+ }
+
+ public void setSelect(Paintable s) {
+ pntbl = s;
+ }
+
+ public void onBrowserEvent(Event event) {
+ super.onBrowserEvent(event);
+ if (client != null) {
+ client.handleTooltipEvent(event, pntbl);
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+package com.itmill.toolkit.terminal.gwt.client.ui;
+
+import java.util.Iterator;
+import java.util.Stack;
+
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.ui.MenuBar;
+import com.google.gwt.user.client.ui.MenuItem;
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;
+import com.itmill.toolkit.terminal.gwt.client.Paintable;
+import com.itmill.toolkit.terminal.gwt.client.UIDL;
+
+public class IMenuBar extends MenuBar implements Paintable {
+
+ /** Set the CSS class name to allow styling. */
+ public static final String CLASSNAME = "i-menubar";
+
+ /** Component identifier in UIDL communications. */
+ protected String uidlId;
+
+ /** Reference to the server connection object. */
+ protected ApplicationConnection client;
+
+ /** A host reference for the Command objects */
+ protected final IMenuBar hostReference = this;
+
+ /**
+ * The constructor should first call super() to initialize the component and
+ * then handle any initialization relevant to IT Mill Toolkit.
+ */
+ public IMenuBar() {
+ // The superclass has a lot of relevant initialization
+ super();
+
+ // This method call of the Paintable interface sets the component
+ // style name in DOM tree
+ setStyleName(CLASSNAME);
+ }
+
+ /**
+ * This method must be implemented to update the client-side component from
+ * UIDL data received from server.
+ *
+ * This method is called when the page is loaded for the first time, and
+ * every time UI changes in the component are received from the server.
+ */
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+ // This call should be made first. Ensure correct implementation,
+ // and let the containing layout manage caption, etc.
+ if (client.updateComponent(this, uidl, true)) {
+ return;
+ }
+
+ // Save reference to server connection object to be able to send
+ // user interaction later
+ this.client = client;
+
+ // Save the UIDL identifier for the component
+ uidlId = uidl.getId();
+
+ // Empty the menu every time it receives new information
+ if (!this.getItems().isEmpty()) {
+ this.clearItems();
+ }
+
+ /* Get tree received from server and actualize it in the GWT-MenuBar */
+
+ // For GWT 1.5
+ // this.setAnimationEnabled(uidl.getBooleanAttribute("animationEnabled"));
+ UIDL items = uidl.getChildUIDL(1);
+ Iterator itr = items.getChildIterator();
+ Stack iteratorStack = new Stack();
+ Stack menuStack = new Stack();
+ MenuBar currentMenu = this;
+
+ // Construct an empty command to be used when the item has no command
+ // associated
+ Command emptyCommand = new Command() {
+ public void execute() {
+ }
+ };
+
+ while (itr.hasNext()) {
+ UIDL item = (UIDL) itr.next();
+ MenuItem menuItem = null; // For receiving the item
+
+ String itemText = item.getStringAttribute("text");
+ final int itemId = item.getIntAttribute("id");
+
+ boolean itemHasCommand = item.getBooleanAttribute("command");
+
+ // Construct html from the text and the optional icon
+ if (!item.hasAttribute("icon")) {
+ itemText = "<p>" + itemText + "</p>";
+ } else {
+ itemText = "<p>"
+ + "<img src=\""
+ + client.translateToolkitUri(item
+ .getStringAttribute("icon")) + "\"</img>"
+ + itemText + "</p>";
+ }
+
+ // Check if we need to attach a command to this item
+ if (itemHasCommand) {
+ // Construct a command that fires onMenuClick(int) with the
+ // item's id-number
+ Command normalCommand = new Command() {
+ public void execute() {
+ hostReference.onMenuClick(itemId);
+ }
+ };
+
+ menuItem = currentMenu.addItem(itemText, true, normalCommand);
+
+ } else {
+ menuItem = currentMenu.addItem(itemText, true, emptyCommand);
+ }
+
+ if (item.getChildCount() > 0) {
+ menuStack.push(currentMenu);
+ iteratorStack.push(itr);
+ itr = item.getChildIterator();
+ currentMenu = new MenuBar(true);
+ menuItem.setSubMenu(currentMenu);
+ }
+
+ if (!itr.hasNext() && !iteratorStack.empty()) {
+ itr = (Iterator) iteratorStack.pop();
+ currentMenu = (MenuBar) menuStack.pop();
+ }
+ }// while
+
+ }// updateFromUIDL
+
+ /**
+ * This is called by the items in the menu and it communicates the
+ * information to the server
+ *
+ * @param clickedItemId
+ * id of the item that was clicked
+ */
+ public void onMenuClick(int clickedItemId) {
+ // Updating the state to the server can not be done before
+ // the server connection is known, i.e., before updateFromUIDL()
+ // has been called.
+ if (uidlId != null && client != null) {
+ // Communicate the user interaction parameters to server. This call
+ // will initiate an AJAX request to the server.
+ client.updateVariable(uidlId, "clickedId", clickedItemId, true);
+ }
+ }
+}// class IMenuBar
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client.ui;
+
+import java.util.Iterator;
+import java.util.Vector;
+
+import com.google.gwt.user.client.ui.Widget;
+import com.itmill.toolkit.terminal.gwt.client.UIDL;
+
+public class INativeSelect extends IOptionGroupBase implements Field {
+
+ public static final String CLASSNAME = "i-select";
+
+ protected TooltipListBox select;
+
+ public INativeSelect() {
+ super(new TooltipListBox(false), CLASSNAME);
+ select = (TooltipListBox) optionsContainer;
+ select.setSelect(this);
+ select.setVisibleItemCount(1);
+ select.addChangeListener(this);
+ select.setStyleName(CLASSNAME + "-select");
+
+ }
+
+ protected void buildOptions(UIDL uidl) {
+ select.setClient(client);
+ select.setEnabled(!isDisabled() && !isReadonly());
+ select.clear();
+ if (isNullSelectionAllowed() && !isNullSelectionItemAvailable()) {
+ // can't unselect last item in singleselect mode
+ select.addItem("", null);
+ }
+ for (final Iterator i = uidl.getChildIterator(); i.hasNext();) {
+ final UIDL optionUidl = (UIDL) i.next();
+ select.addItem(optionUidl.getStringAttribute("caption"), optionUidl
+ .getStringAttribute("key"));
+ if (optionUidl.hasAttribute("selected")) {
+ select.setItemSelected(select.getItemCount() - 1, true);
+ }
+ }
+ }
+
+ protected Object[] getSelectedItems() {
+ final Vector selectedItemKeys = new Vector();
+ for (int i = 0; i < select.getItemCount(); i++) {
+ if (select.isItemSelected(i)) {
+ selectedItemKeys.add(select.getValue(i));
+ }
+ }
+ return selectedItemKeys.toArray();
+ }
+
+ public void onChange(Widget sender) {
+ if (select.isMultipleSelect()) {
+ client.updateVariable(id, "selected", getSelectedItems(),
+ isImmediate());
+ } else {
+ client.updateVariable(id, "selected", new String[] { ""
+ + getSelectedItem() }, isImmediate());
+ }
+ }
+
+ public void setHeight(String height) {
+ select.setHeight(height);
+ super.setHeight(height);
+ }
+
+ public void setWidth(String width) {
+ select.setWidth(width);
+ super.setWidth(width);
+ }
+
+}
--- /dev/null
+/* \r
+@ITMillApache2LicenseForJavaFiles@\r
+ */\r
+\r
+package com.itmill.toolkit.terminal.gwt.client.ui;\r
+\r
+import java.util.HashMap;\r
+import java.util.Iterator;\r
+import java.util.Map;\r
+\r
+import com.google.gwt.user.client.ui.CheckBox;\r
+import com.google.gwt.user.client.ui.Panel;\r
+import com.google.gwt.user.client.ui.RadioButton;\r
+import com.google.gwt.user.client.ui.Widget;\r
+import com.itmill.toolkit.terminal.gwt.client.UIDL;\r
+\r
+public class IOptionGroup extends IOptionGroupBase {\r
+\r
+ public static final String CLASSNAME = "i-select-optiongroup";\r
+\r
+ private final Panel panel;\r
+\r
+ private final Map optionsToKeys;\r
+\r
+ public IOptionGroup() {\r
+ super(CLASSNAME);\r
+ panel = (Panel) optionsContainer;\r
+ optionsToKeys = new HashMap();\r
+ }\r
+\r
+ /*\r
+ * Return true if no elements were changed, false otherwise.\r
+ */\r
+ protected void buildOptions(UIDL uidl) {\r
+ panel.clear();\r
+ for (final Iterator it = uidl.getChildIterator(); it.hasNext();) {\r
+ final UIDL opUidl = (UIDL) it.next();\r
+ CheckBox op;\r
+ if (isMultiselect()) {\r
+ op = new ICheckBox();\r
+ op.setText(opUidl.getStringAttribute("caption"));\r
+ } else {\r
+ op = new RadioButton(id, opUidl.getStringAttribute("caption"));\r
+ op.setStyleName("i-radiobutton");\r
+ }\r
+ op.addStyleName(CLASSNAME_OPTION);\r
+ op.setChecked(opUidl.getBooleanAttribute("selected"));\r
+ op.setEnabled(!opUidl.getBooleanAttribute("disabled")\r
+ && !isReadonly() && !isDisabled());\r
+ op.addClickListener(this);\r
+ optionsToKeys.put(op, opUidl.getStringAttribute("key"));\r
+ panel.add(op);\r
+ }\r
+ }\r
+\r
+ protected Object[] getSelectedItems() {\r
+ return selectedKeys.toArray();\r
+ }\r
+\r
+ public void onClick(Widget sender) {\r
+ super.onClick(sender);\r
+ if (sender instanceof CheckBox) {\r
+ final boolean selected = ((CheckBox) sender).isChecked();\r
+ final String key = (String) optionsToKeys.get(sender);\r
+ if (!isMultiselect()) {\r
+ selectedKeys.clear();\r
+ }\r
+ if (selected) {\r
+ selectedKeys.add(key);\r
+ } else {\r
+ selectedKeys.remove(key);\r
+ }\r
+ client.updateVariable(id, "selected", getSelectedItems(),\r
+ isImmediate());\r
+ }\r
+ }\r
+\r
+}\r
--- /dev/null
+/* \r
+@ITMillApache2LicenseForJavaFiles@\r
+ */\r
+\r
+package com.itmill.toolkit.terminal.gwt.client.ui;\r
+\r
+import java.util.Set;\r
+\r
+import com.google.gwt.user.client.ui.ChangeListener;\r
+import com.google.gwt.user.client.ui.ClickListener;\r
+import com.google.gwt.user.client.ui.Composite;\r
+import com.google.gwt.user.client.ui.FlowPanel;\r
+import com.google.gwt.user.client.ui.KeyboardListener;\r
+import com.google.gwt.user.client.ui.Panel;\r
+import com.google.gwt.user.client.ui.Widget;\r
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;\r
+import com.itmill.toolkit.terminal.gwt.client.Paintable;\r
+import com.itmill.toolkit.terminal.gwt.client.UIDL;\r
+\r
+abstract class IOptionGroupBase extends Composite implements Paintable, Field,\r
+ ClickListener, ChangeListener, KeyboardListener {\r
+\r
+ public static final String CLASSNAME_OPTION = "i-select-option";\r
+\r
+ protected ApplicationConnection client;\r
+\r
+ protected String id;\r
+\r
+ protected Set selectedKeys;\r
+\r
+ private boolean immediate;\r
+\r
+ private boolean multiselect;\r
+\r
+ private boolean disabled;\r
+\r
+ private boolean readonly;\r
+\r
+ private int cols = 0;\r
+\r
+ private int rows = 0;\r
+\r
+ private boolean nullSelectionAllowed = true;\r
+\r
+ private boolean nullSelectionItemAvailable = false;\r
+\r
+ /**\r
+ * Widget holding the different options (e.g. ListBox or Panel for radio\r
+ * buttons) (optional, fallbacks to container Panel)\r
+ */\r
+ protected Widget optionsContainer;\r
+\r
+ /**\r
+ * Panel containing the component\r
+ */\r
+ private final Panel container;\r
+\r
+ private ITextField newItemField;\r
+\r
+ private IButton newItemButton;\r
+\r
+ public IOptionGroupBase(String classname) {\r
+ container = new FlowPanel();\r
+ initWidget(container);\r
+ optionsContainer = container;\r
+ container.setStyleName(classname);\r
+ immediate = false;\r
+ multiselect = false;\r
+ }\r
+\r
+ /*\r
+ * Call this if you wish to specify your own container for the option\r
+ * elements (e.g. SELECT)\r
+ */\r
+ public IOptionGroupBase(Widget w, String classname) {\r
+ this(classname);\r
+ optionsContainer = w;\r
+ container.add(optionsContainer);\r
+ }\r
+\r
+ protected boolean isImmediate() {\r
+ return immediate;\r
+ }\r
+\r
+ protected boolean isMultiselect() {\r
+ return multiselect;\r
+ }\r
+\r
+ protected boolean isDisabled() {\r
+ return disabled;\r
+ }\r
+\r
+ protected boolean isReadonly() {\r
+ return readonly;\r
+ }\r
+\r
+ protected boolean isNullSelectionAllowed() {\r
+ return nullSelectionAllowed;\r
+ }\r
+\r
+ protected boolean isNullSelectionItemAvailable() {\r
+ return nullSelectionItemAvailable;\r
+ }\r
+\r
+ /**\r
+ * @return "cols" specified in uidl, 0 if not specified\r
+ */\r
+ protected int getColumns() {\r
+ return cols;\r
+ }\r
+\r
+ /**\r
+ * @return "rows" specified in uidl, 0 if not specified\r
+ */\r
+\r
+ protected int getRows() {\r
+ return rows;\r
+ }\r
+\r
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {\r
+ this.client = client;\r
+ id = uidl.getId();\r
+\r
+ if (client.updateComponent(this, uidl, true)) {\r
+ return;\r
+ }\r
+\r
+ selectedKeys = uidl.getStringArrayVariableAsSet("selected");\r
+\r
+ readonly = uidl.getBooleanAttribute("readonly");\r
+ disabled = uidl.getBooleanAttribute("disabled");\r
+ multiselect = "multi".equals(uidl.getStringAttribute("selectmode"));\r
+ immediate = uidl.getBooleanAttribute("immediate");\r
+ nullSelectionAllowed = uidl.getBooleanAttribute("nullselect");\r
+ nullSelectionItemAvailable = uidl.getBooleanAttribute("nullselectitem");\r
+\r
+ cols = uidl.getIntAttribute("cols");\r
+ rows = uidl.getIntAttribute("rows");\r
+\r
+ final UIDL ops = uidl.getChildUIDL(0);\r
+\r
+ if (getColumns() > 0) {\r
+ container.setWidth(getColumns() + "em");\r
+ if (container != optionsContainer) {\r
+ optionsContainer.setWidth("100%");\r
+ }\r
+ }\r
+\r
+ buildOptions(ops);\r
+\r
+ if (uidl.getBooleanAttribute("allownewitem")) {\r
+ if (newItemField == null) {\r
+ newItemButton = new IButton();\r
+ newItemButton.setText("+");\r
+ newItemButton.setWidth("1.5em");\r
+ newItemButton.addClickListener(this);\r
+ newItemField = new ITextField();\r
+ newItemField.addKeyboardListener(this);\r
+ // newItemField.setColumns(16);\r
+ if (getColumns() > 0) {\r
+ newItemField.setWidth((getColumns() - 2) + "em");\r
+ }\r
+ }\r
+ newItemField.setEnabled(!disabled && !readonly);\r
+ newItemButton.setEnabled(!disabled && !readonly);\r
+\r
+ if (newItemField == null || newItemField.getParent() != container) {\r
+ container.add(newItemField);\r
+ container.add(newItemButton);\r
+ }\r
+ } else if (newItemField != null) {\r
+ container.remove(newItemField);\r
+ container.remove(newItemButton);\r
+ }\r
+\r
+ }\r
+\r
+ public void onClick(Widget sender) {\r
+ if (sender == newItemButton && !newItemField.getText().equals("")) {\r
+ client.updateVariable(id, "newitem", newItemField.getText(), true);\r
+ newItemField.setText("");\r
+ }\r
+ }\r
+\r
+ public void onChange(Widget sender) {\r
+ if (multiselect) {\r
+ client\r
+ .updateVariable(id, "selected", getSelectedItems(),\r
+ immediate);\r
+ } else {\r
+ client.updateVariable(id, "selected", new String[] { ""\r
+ + getSelectedItem() }, immediate);\r
+ }\r
+ }\r
+\r
+ public void onKeyPress(Widget sender, char keyCode, int modifiers) {\r
+ if (sender == newItemField && keyCode == KeyboardListener.KEY_ENTER) {\r
+ newItemButton.click();\r
+ }\r
+ }\r
+\r
+ public void onKeyUp(Widget sender, char keyCode, int modifiers) {\r
+ // Ignore, subclasses may override\r
+ }\r
+\r
+ public void onKeyDown(Widget sender, char keyCode, int modifiers) {\r
+ // Ignore, subclasses may override\r
+ }\r
+\r
+ protected abstract void buildOptions(UIDL uidl);\r
+\r
+ protected abstract Object[] getSelectedItems();\r
+\r
+ protected Object getSelectedItem() {\r
+ final Object[] sel = getSelectedItems();\r
+ if (sel.length > 0) {\r
+ return sel[0];\r
+ } else {\r
+ return null;\r
+ }\r
+ }\r
+\r
+}\r
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client.ui;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.ui.ComplexPanel;
+import com.google.gwt.user.client.ui.UIObject;
+import com.google.gwt.user.client.ui.Widget;
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;
+import com.itmill.toolkit.terminal.gwt.client.Caption;
+import com.itmill.toolkit.terminal.gwt.client.Container;
+import com.itmill.toolkit.terminal.gwt.client.Paintable;
+import com.itmill.toolkit.terminal.gwt.client.StyleConstants;
+import com.itmill.toolkit.terminal.gwt.client.UIDL;
+import com.itmill.toolkit.terminal.gwt.client.Util;
+
+/**
+ * Abstract base class for ordered layouts. Use either vertical or horizontal
+ * subclass.
+ *
+ * @author IT Mill Ltd
+ */
+public abstract class IOrderedLayout extends ComplexPanel implements Container {
+
+ public static final String CLASSNAME = "i-orderedlayout";
+
+ public static final int ORIENTATION_VERTICAL = 0;
+ public static final int ORIENTATION_HORIZONTAL = 1;
+
+ int orientationMode = ORIENTATION_VERTICAL;
+
+ protected HashMap componentToCaption = new HashMap();
+
+ protected ApplicationConnection client;
+
+ /**
+ * Contains reference to Element where Paintables are wrapped. Normally a TR
+ * or a TBODY element.
+ */
+ protected Element childContainer;
+
+ /*
+ * Elements that provides the Layout interface implementation.
+ */
+ protected Element root;
+ protected Element margin;
+
+ private boolean hasComponentSpacing;
+
+ private MarginInfo margins = new MarginInfo(0);
+
+ public IOrderedLayout(int orientation) {
+ orientationMode = orientation;
+ constructDOM();
+ setStyleName(CLASSNAME);
+ }
+
+ protected void constructDOM() {
+ root = DOM.createDiv();
+ margin = DOM.createDiv();
+ DOM.appendChild(root, margin);
+ if (orientationMode == ORIENTATION_HORIZONTAL) {
+ final String structure = "<table cellspacing=\"0\" cellpadding=\"0\"><tbody><tr></tr></tbody></table>";
+ DOM.setInnerHTML(margin, structure);
+ childContainer = DOM.getFirstChild(DOM.getFirstChild(DOM
+ .getFirstChild(margin)));
+ } else {
+ childContainer = margin;
+ }
+ setElement(root);
+ }
+
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+
+ this.client = client;
+
+ // Ensure correct implementation
+ if (client.updateComponent(this, uidl, false)) {
+ return;
+ }
+
+ // Handle layout margins
+ if (margins.getBitMask() != uidl.getIntAttribute("margins")) {
+ handleMargins(uidl);
+ }
+
+ //
+ hasComponentSpacing = uidl.getBooleanAttribute("spacing");
+
+ // Update contained components
+
+ final ArrayList uidlWidgets = new ArrayList();
+ for (final Iterator it = uidl.getChildIterator(); it.hasNext();) {
+ final UIDL uidlForChild = (UIDL) it.next();
+ final Paintable child = client.getPaintable(uidlForChild);
+ uidlWidgets.add(child);
+ }
+
+ final ArrayList oldWidgets = getPaintables();
+
+ final Iterator oldIt = oldWidgets.iterator();
+ final Iterator newIt = uidlWidgets.iterator();
+ final Iterator newUidl = uidl.getChildIterator();
+
+ final ArrayList paintedWidgets = new ArrayList();
+
+ Widget oldChild = null;
+ while (newIt.hasNext()) {
+ final Widget child = (Widget) newIt.next();
+ final UIDL childUidl = (UIDL) newUidl.next();
+
+ if (oldChild == null && oldIt.hasNext()) {
+ // search for next old Paintable which still exists in layout
+ // and delete others
+ while (oldIt.hasNext()) {
+ oldChild = (Widget) oldIt.next();
+ // now oldChild is an instance of Paintable
+ if (paintedWidgets.contains(oldChild)) {
+ continue;
+ } else if (uidlWidgets.contains(oldChild)) {
+ break;
+ } else {
+ removePaintable((Paintable) oldChild);
+ oldChild = null;
+ }
+ }
+ }
+ if (oldChild == null) {
+ // we are adding components to layout
+ add(child);
+ } else if (child == oldChild) {
+ // child already attached and updated
+ oldChild = null;
+ } else if (hasChildComponent(child)) {
+ // current child has been moved, re-insert before current
+ // oldChild
+ // TODO this might be optimized by moving only container element
+ // to correct position
+ removeCaption(child);
+ int index = getPaintableIndex(oldChild);
+ if (componentToCaption.containsKey(oldChild)) {
+ index--;
+ }
+ remove(child);
+ this.insert(child, index);
+ } else {
+ // insert new child before old one
+ final int index = getPaintableIndex(oldChild); // TODO this
+ // returns wrong
+ // value if
+ // captions are
+ // used
+ insert(child, index);
+ }
+ ((Paintable) child).updateFromUIDL(childUidl, client);
+ paintedWidgets.add(child);
+ }
+ // remove possibly remaining old Paintable object which were not updated
+ while (oldIt.hasNext()) {
+ oldChild = (Widget) oldIt.next();
+ final Paintable p = (Paintable) oldChild;
+ if (!uidlWidgets.contains(p)) {
+ removePaintable(p);
+ }
+ }
+
+ // Handle component alignments
+ handleAlignments(uidl);
+ }
+
+ /**
+ * Retuns a list of Paintables currently rendered in layout
+ *
+ * @return list of Paintable objects
+ */
+ protected ArrayList getPaintables() {
+ final ArrayList al = new ArrayList();
+ final Iterator it = iterator();
+ while (it.hasNext()) {
+ final Widget w = (Widget) it.next();
+ if (w instanceof Paintable) {
+ al.add(w);
+ }
+ }
+ return al;
+ }
+
+ /**
+ * Removes Paintable from DOM and its reference from ApplicationConnection.
+ *
+ * Also removes Paintable's Caption if one exists
+ *
+ * @param p
+ * Paintable to be removed
+ */
+ public boolean removePaintable(Paintable p) {
+ final Caption c = (Caption) componentToCaption.get(p);
+ if (c != null) {
+ componentToCaption.remove(c);
+ remove(c);
+ }
+ client.unregisterPaintable(p);
+ return remove((Widget) p);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.itmill.toolkit.terminal.gwt.client.Layout#replaceChildComponent(com.google.gwt.user.client.ui.Widget,
+ * com.google.gwt.user.client.ui.Widget)
+ */
+ public void replaceChildComponent(Widget from, Widget to) {
+ client.unregisterPaintable((Paintable) from);
+ final Caption c = (Caption) componentToCaption.get(from);
+ if (c != null) {
+ remove(c);
+ componentToCaption.remove(c);
+ }
+ final int index = getPaintableIndex(from);
+ if (index >= 0) {
+ remove(index);
+ insert(to, index);
+ }
+ }
+
+ protected void insert(Widget w, int beforeIndex) {
+ if (w instanceof Caption) {
+ final Caption c = (Caption) w;
+ // captions go into same container element as their
+ // owners
+ final Element container = getWidgetWrapperFor((Widget) c.getOwner())
+ .getContainerElement();
+ final Element captionContainer = DOM.createDiv();
+ DOM.insertChild(container, captionContainer, 0);
+ insert(w, captionContainer, beforeIndex, false);
+ } else {
+ WidgetWrapper wr = new WidgetWrapper();
+ DOM.insertChild(childContainer, wr.getElement(), beforeIndex);
+ insert(w, wr.getContainerElement(), beforeIndex, false);
+ }
+ }
+
+ public boolean hasChildComponent(Widget component) {
+ return getPaintableIndex(component) >= 0;
+ }
+
+ public void updateCaption(Paintable component, UIDL uidl) {
+
+ Caption c = (Caption) componentToCaption.get(component);
+
+ if (Caption.isNeeded(uidl)) {
+ if (c == null) {
+ final int index = getPaintableIndex((Widget) component);
+ c = new Caption(component, client);
+ insert(c, index);
+ componentToCaption.put(component, c);
+ }
+ c.updateCaption(uidl);
+ } else {
+ if (c != null) {
+ remove(c);
+ componentToCaption.remove(component);
+ }
+ }
+ }
+
+ public void removeCaption(Widget w) {
+ final Caption c = (Caption) componentToCaption.get(w);
+ if (c != null) {
+ this.remove(c);
+ componentToCaption.remove(w);
+ }
+ }
+
+ public void add(Widget w) {
+ WidgetWrapper wr = new WidgetWrapper();
+ DOM.appendChild(childContainer, wr.getElement());
+ super.add(w, wr.getContainerElement());
+ }
+
+ public boolean remove(int index) {
+ return remove(getWidget(index));
+ }
+
+ public boolean remove(Widget w) {
+ final Element wrapper = getWidgetWrapperFor(w).getElement();
+ final boolean removed = super.remove(w);
+ if (removed) {
+ if (!(w instanceof Caption)) {
+ DOM.removeChild(childContainer, wrapper);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ public Widget getWidget(int index) {
+ return getChildren().get(index);
+ }
+
+ public int getWidgetCount() {
+ return getChildren().size();
+ }
+
+ public int getWidgetIndex(Widget child) {
+ return getChildren().indexOf(child);
+ }
+
+ public int getPaintableCount() {
+ int size = 0;
+ for (Iterator it = getChildren().iterator(); it.hasNext();) {
+ Widget w = (Widget) it.next();
+ if (!(w instanceof Caption)) {
+ size++;
+ }
+ }
+ return size;
+ }
+
+ public int getPaintableIndex(Widget child) {
+ int i = 0;
+ for (Iterator it = getChildren().iterator(); it.hasNext();) {
+ Widget w = (Widget) it.next();
+ if (w == child) {
+ return i;
+ } else if (!(w instanceof Caption)) {
+ i++;
+ }
+ }
+ return -1;
+ }
+
+ protected void handleMargins(UIDL uidl) {
+ margins = new MarginInfo(uidl.getIntAttribute("margins"));
+ setStyleName(margin, CLASSNAME + "-" + StyleConstants.MARGIN_TOP,
+ margins.hasTop());
+ setStyleName(margin, CLASSNAME + "-" + StyleConstants.MARGIN_RIGHT,
+ margins.hasRight());
+ setStyleName(margin, CLASSNAME + "-" + StyleConstants.MARGIN_BOTTOM,
+ margins.hasBottom());
+ setStyleName(margin, CLASSNAME + "-" + StyleConstants.MARGIN_LEFT,
+ margins.hasLeft());
+ }
+
+ protected void handleAlignments(UIDL uidl) {
+ // Component alignments as a comma separated list.
+ // See com.itmill.toolkit.terminal.gwt.client.ui.AlignmentInfo.java for
+ // possible values.
+ final int[] alignments = uidl.getIntArrayAttribute("alignments");
+ int alignmentIndex = 0;
+ // Insert alignment attributes
+ final Iterator it = getPaintables().iterator();
+ boolean first = true;
+ while (it.hasNext()) {
+
+ // Calculate alignment info
+ final AlignmentInfo ai = new AlignmentInfo(
+ alignments[alignmentIndex++]);
+
+ final WidgetWrapper wr = getWidgetWrapperFor((Widget) it.next());
+ wr.setAlignment(ai.getVerticalAlignment(), ai
+ .getHorizontalAlignment());
+
+ // Handle spacing in this loop as well
+ if (first) {
+ wr.setSpacingEnabled(false);
+ first = false;
+ } else {
+ wr.setSpacingEnabled(hasComponentSpacing);
+ }
+ }
+ }
+
+ /**
+ * WidgetWrapper classe. Helper classe for spacing and alignment handling.
+ *
+ */
+ class WidgetWrapper extends UIObject {
+
+ Element td;
+
+ public WidgetWrapper() {
+ if (orientationMode == ORIENTATION_VERTICAL) {
+ setElement(DOM.createDiv());
+ // Apply 'hasLayout' for IE (needed to get accurate dimension
+ // calculations)
+ if (Util.isIE()) {
+ DOM.setStyleAttribute(getElement(), "zoom", "1");
+ }
+ } else {
+ setElement(DOM.createTD());
+ }
+ }
+
+ public WidgetWrapper(Element element) {
+ if (DOM.getElementProperty(element, "className").equals("i_align")) {
+ td = element;
+ setElement(DOM.getParent(DOM.getParent(DOM.getParent(DOM
+ .getParent(td)))));
+ } else {
+ setElement(element);
+ }
+ }
+
+ Element getContainerElement() {
+ if (td != null) {
+ return td;
+ } else {
+ return getElement();
+ }
+ }
+
+ void setAlignment(String verticalAlignment, String horizontalAlignment) {
+
+ // Set vertical alignment
+ if (Util.isIE()) {
+ DOM.setElementAttribute(getElement(), "vAlign",
+ verticalAlignment);
+ } else {
+ DOM.setStyleAttribute(getElement(), "verticalAlign",
+ verticalAlignment);
+ }
+
+ // Set horizontal alignment
+ if (Util.isIE()) {
+ DOM.setElementAttribute(getElement(), "align",
+ horizontalAlignment);
+ } else {
+ // use one-cell table to implement horizontal alignments, only
+ // for values other than "left" (which is default)
+ // build one cell table
+ if (!horizontalAlignment.equals("left")) {
+ if (td == null) {
+ final Element table = DOM.createTable();
+ final Element tBody = DOM.createTBody();
+ final Element tr = DOM.createTR();
+ td = DOM.createTD();
+ DOM.appendChild(table, tBody);
+ DOM.appendChild(tBody, tr);
+ DOM.appendChild(tr, td);
+ DOM.setElementAttribute(table, "cellpadding", "0");
+ DOM.setElementAttribute(table, "cellspacing", "0");
+ DOM.setStyleAttribute(table, "width", "100%");
+ // use className for identification
+ DOM.setElementProperty(td, "className", "i_align");
+ // move possible content to cell
+ while (DOM.getChildCount(getElement()) > 0) {
+ Element content = DOM.getFirstChild(getElement());
+ if (content != null) {
+ DOM.removeChild(getElement(), content);
+ DOM.appendChild(td, content);
+ }
+ }
+ DOM.appendChild(getElement(), table);
+ }
+ DOM.setElementAttribute(td, "align", horizontalAlignment);
+ } else if (td != null) {
+ // Move content to main container
+ while (DOM.getChildCount(td) > 0) {
+ Element content = DOM.getFirstChild(td);
+ if (content != null) {
+ DOM.removeChild(td, content);
+ DOM.appendChild(getElement(), content);
+ }
+ }
+ // Remove unneeded table element
+ DOM.removeChild(getElement(), DOM
+ .getFirstChild(getElement()));
+ }
+ }
+ }
+
+ void setSpacingEnabled(boolean b) {
+ setStyleName(
+ getElement(),
+ CLASSNAME
+ + "-"
+ + (orientationMode == ORIENTATION_HORIZONTAL ? StyleConstants.HORIZONTAL_SPACING
+ : StyleConstants.VERTICAL_SPACING), b);
+ }
+
+ }
+
+ /**
+ * Returns given widgets WidgetWrapper
+ *
+ * @param child
+ * @return
+ */
+ public WidgetWrapper getWidgetWrapperFor(Widget child) {
+ final Element containerElement = DOM.getParent(child.getElement());
+ return new WidgetWrapper(containerElement);
+ }
+
+}
--- /dev/null
+/* \r
+@ITMillApache2LicenseForJavaFiles@\r
+ */\r
+\r
+package com.itmill.toolkit.terminal.gwt.client.ui;\r
+\r
+import com.google.gwt.user.client.DOM;\r
+import com.itmill.toolkit.terminal.gwt.client.ContainerResizedListener;\r
+import com.itmill.toolkit.terminal.gwt.client.UIDL;\r
+import com.itmill.toolkit.terminal.gwt.client.Util;\r
+\r
+public class IOrderedLayoutHorizontal extends IOrderedLayout implements\r
+ ContainerResizedListener {\r
+\r
+ private String height;\r
+ private boolean relativeHeight;\r
+ private int marginHeight = 0;\r
+\r
+ public IOrderedLayoutHorizontal() {\r
+ super(ORIENTATION_HORIZONTAL);\r
+ }\r
+\r
+ public void setHeight(String newHeight) {\r
+ super.setHeight(newHeight);\r
+ if (newHeight != null && !newHeight.equals("")) {\r
+ if (!newHeight.equals(height)) {\r
+ height = newHeight;\r
+ if (newHeight.indexOf("%") > 0) {\r
+ relativeHeight = true;\r
+ DOM.setStyleAttribute(getElement(), "overflow", "hidden");\r
+ } else {\r
+ relativeHeight = false;\r
+ DOM.setStyleAttribute(getElement(), "overflow", "");\r
+ }\r
+ setInternalHeight();\r
+ }\r
+ } else {\r
+ if (newHeight != null) {\r
+ // clear existing height values\r
+ DOM.setStyleAttribute(getElement(), "overflow", "");\r
+ DOM.setStyleAttribute(DOM.getFirstChild(margin), "height", "");\r
+\r
+ newHeight = null;\r
+ relativeHeight = false;\r
+ }\r
+ }\r
+ }\r
+\r
+ protected void handleMargins(UIDL uidl) {\r
+ super.handleMargins(uidl);\r
+ if (height != null) {\r
+ marginHeight = -1;\r
+ setInternalHeight();\r
+ }\r
+ }\r
+\r
+ private void setInternalHeight() {\r
+ int availSpace = DOM\r
+ .getElementPropertyInt(getElement(), "clientHeight");\r
+ if (marginHeight < 0) {\r
+ DOM.setStyleAttribute(margin, "height", height);\r
+ int tmp = DOM.getElementPropertyInt(margin, "offsetHeight");\r
+ marginHeight = tmp\r
+ - DOM.getElementPropertyInt(getElement(), "clientHeight");\r
+ DOM.setStyleAttribute(margin, "height", "");\r
+ }\r
+\r
+ availSpace -= marginHeight;\r
+\r
+ DOM.setStyleAttribute(DOM.getFirstChild(margin), "height", availSpace\r
+ + "px");\r
+\r
+ }\r
+\r
+ public void iLayout() {\r
+ if (relativeHeight) {\r
+ setInternalHeight();\r
+ }\r
+ Util.runDescendentsLayout(this);\r
+ }\r
+}\r
--- /dev/null
+/* \r
+@ITMillApache2LicenseForJavaFiles@\r
+ */\r
+\r
+package com.itmill.toolkit.terminal.gwt.client.ui;\r
+\r
+public class IOrderedLayoutVertical extends IOrderedLayout {\r
+\r
+ public IOrderedLayoutVertical() {\r
+ super(ORIENTATION_VERTICAL);\r
+ }\r
+}\r
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client.ui;
+
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;
+import com.itmill.toolkit.terminal.gwt.client.BrowserInfo;
+import com.itmill.toolkit.terminal.gwt.client.ContainerResizedListener;
+import com.itmill.toolkit.terminal.gwt.client.ErrorMessage;
+import com.itmill.toolkit.terminal.gwt.client.Paintable;
+import com.itmill.toolkit.terminal.gwt.client.UIDL;
+import com.itmill.toolkit.terminal.gwt.client.Util;
+
+public class IPanel extends SimplePanel implements Paintable,
+ ContainerResizedListener {
+
+ public static final String CLASSNAME = "i-panel";
+
+ ApplicationConnection client;
+
+ String id;
+
+ private final Element captionNode = DOM.createDiv();
+
+ private final Element captionText = DOM.createSpan();
+
+ private Icon icon;
+
+ private final Element bottomDecoration = DOM.createDiv();
+
+ private final Element contentNode = DOM.createDiv();
+
+ private Element errorIndicatorElement;
+
+ private ErrorMessage errorMessage;
+
+ private String height;
+
+ private Paintable layout;
+
+ ShortcutActionHandler shortcutHandler;
+
+ private String width;
+
+ private Element geckoCaptionMeter;
+
+ public IPanel() {
+ super();
+ DOM.appendChild(getElement(), captionNode);
+ DOM.appendChild(captionNode, captionText);
+ DOM.appendChild(getElement(), contentNode);
+ DOM.appendChild(getElement(), bottomDecoration);
+ setStyleName(CLASSNAME);
+ DOM
+ .setElementProperty(captionNode, "className", CLASSNAME
+ + "-caption");
+ DOM
+ .setElementProperty(contentNode, "className", CLASSNAME
+ + "-content");
+ DOM.setElementProperty(bottomDecoration, "className", CLASSNAME
+ + "-deco");
+ DOM.sinkEvents(getElement(), Event.ONKEYDOWN);
+ }
+
+ protected Element getContainerElement() {
+ return contentNode;
+ }
+
+ private void setCaption(String text) {
+ DOM.setInnerText(captionText, text);
+ }
+
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+ // Ensure correct implementation
+ if (client.updateComponent(this, uidl, false)) {
+ return;
+ }
+
+ this.client = client;
+ id = uidl.getId();
+
+ // Panel size. Height needs to be saved for later use
+ height = uidl.hasVariable("height") ? uidl.getStringVariable("height")
+ : null;
+ setWidth(uidl.hasVariable("width") ? uidl.getStringVariable("width")
+ : "");
+
+ // Restore default stylenames
+ DOM
+ .setElementProperty(captionNode, "className", CLASSNAME
+ + "-caption");
+ DOM
+ .setElementProperty(contentNode, "className", CLASSNAME
+ + "-content");
+ DOM.setElementProperty(bottomDecoration, "className", CLASSNAME
+ + "-deco");
+
+ // Handle caption displaying
+ boolean hasCaption = false;
+ if (uidl.hasAttribute("caption")
+ && !uidl.getStringAttribute("caption").equals("")) {
+ setCaption(uidl.getStringAttribute("caption"));
+ hasCaption = true;
+ } else {
+ setCaption("");
+ DOM.setElementProperty(captionNode, "className", CLASSNAME
+ + "-nocaption");
+ }
+
+ setIconUri(uidl, client);
+
+ handleDescription(uidl);
+
+ handleError(uidl);
+
+ // Add proper stylenames for all elements. This way we can prevent
+ // unwanted CSS selector inheritance.
+ if (uidl.hasAttribute("style")) {
+ final String[] styles = uidl.getStringAttribute("style").split(" ");
+ final String captionBaseClass = CLASSNAME
+ + (hasCaption ? "-caption" : "-nocaption");
+ final String contentBaseClass = CLASSNAME + "-content";
+ final String decoBaseClass = CLASSNAME + "-deco";
+ String captionClass = captionBaseClass;
+ String contentClass = contentBaseClass;
+ String decoClass = decoBaseClass;
+ for (int i = 0; i < styles.length; i++) {
+ captionClass += " " + captionBaseClass + "-" + styles[i];
+ contentClass += " " + contentBaseClass + "-" + styles[i];
+ decoClass += " " + decoBaseClass + "-" + styles[i];
+ }
+ DOM.setElementProperty(captionNode, "className", captionClass);
+ DOM.setElementProperty(contentNode, "className", contentClass);
+ DOM.setElementProperty(bottomDecoration, "className", decoClass);
+ }
+
+ // Height adjustment
+ iLayout(false);
+
+ // Render content
+ final UIDL layoutUidl = uidl.getChildUIDL(0);
+ final Paintable newLayout = client.getPaintable(layoutUidl);
+ if (newLayout != layout) {
+ if (layout != null) {
+ client.unregisterPaintable(layout);
+ }
+ setWidget((Widget) newLayout);
+ layout = newLayout;
+ }
+ (layout).updateFromUIDL(layoutUidl, client);
+
+ // We may have actions attached to this panel
+ if (uidl.getChildCount() > 1) {
+ final int cnt = uidl.getChildCount();
+ for (int i = 1; i < cnt; i++) {
+ UIDL childUidl = uidl.getChildUIDL(i);
+ if (childUidl.getTag().equals("actions")) {
+ if (shortcutHandler == null) {
+ shortcutHandler = new ShortcutActionHandler(id, client);
+ }
+ shortcutHandler.updateActionMap(childUidl);
+ }
+ }
+ }
+
+ }
+
+ private void handleError(UIDL uidl) {
+ if (uidl.hasAttribute("error")) {
+ final UIDL errorUidl = uidl.getErrors();
+ if (errorIndicatorElement == null) {
+ errorIndicatorElement = DOM.createDiv();
+ DOM.setElementProperty(errorIndicatorElement, "className",
+ "i-errorindicator");
+ DOM.sinkEvents(errorIndicatorElement, Event.MOUSEEVENTS);
+ sinkEvents(Event.MOUSEEVENTS);
+ }
+ DOM.insertBefore(captionNode, errorIndicatorElement, captionText);
+ if (errorMessage == null) {
+ errorMessage = new ErrorMessage();
+ }
+ errorMessage.updateFromUIDL(errorUidl);
+
+ } else if (errorIndicatorElement != null) {
+ DOM.removeChild(captionNode, errorIndicatorElement);
+ errorIndicatorElement = null;
+ }
+ }
+
+ private void handleDescription(UIDL uidl) {
+ DOM.setElementProperty(captionText, "title", uidl
+ .hasAttribute("description") ? uidl
+ .getStringAttribute("description") : "");
+ }
+
+ private void setIconUri(UIDL uidl, ApplicationConnection client) {
+ final String iconUri = uidl.hasAttribute("icon") ? uidl
+ .getStringAttribute("icon") : null;
+ if (iconUri == null) {
+ if (icon != null) {
+ DOM.removeChild(captionNode, icon.getElement());
+ icon = null;
+ }
+ } else {
+ if (icon == null) {
+ icon = new Icon(client);
+ DOM.insertChild(captionNode, icon.getElement(), 0);
+ }
+ icon.setUri(iconUri);
+ }
+ }
+
+ public void iLayout() {
+ iLayout(true);
+ }
+
+ public void iLayout(boolean runGeckoFix) {
+ if (height != null && height != "") {
+ final boolean hasChildren = getWidget() != null;
+ Element contentEl = null;
+ String origPositioning = null;
+ // save scroll position
+ int scrollTop = DOM.getElementPropertyInt(contentNode, "scrollTop");
+ int scrollLeft = DOM.getElementPropertyInt(contentNode,
+ "scrollLeft");
+ if (hasChildren) {
+ // Remove children temporary form normal flow to detect proper
+ // size
+ contentEl = getWidget().getElement();
+ origPositioning = DOM.getStyleAttribute(contentEl, "position");
+ DOM.setStyleAttribute(contentEl, "position", "absolute");
+ }
+
+ // Set defaults
+ DOM.setStyleAttribute(contentNode, "overflow", "hidden");
+ DOM.setStyleAttribute(contentNode, "height", "");
+
+ // Calculate target height
+ super.setHeight(height);
+ final int targetHeight = getOffsetHeight();
+
+ // Calculate used height
+ super.setHeight("");
+ final int usedHeight = DOM.getElementPropertyInt(bottomDecoration,
+ "offsetTop")
+ + DOM.getElementPropertyInt(bottomDecoration,
+ "offsetHeight")
+ - DOM.getElementPropertyInt(getElement(), "offsetTop");
+
+ // Calculate content area height (don't allow negative values)
+ int h = targetHeight - usedHeight;
+ if (h < 0) {
+ h = 0;
+ }
+
+ // Set proper values for content element
+ DOM.setStyleAttribute(contentNode, "height", h + "px");
+ DOM.setStyleAttribute(contentNode, "overflow", "auto");
+
+ // Restore content to flow
+ if (hasChildren) {
+ ApplicationConnection.getConsole().log(
+ "positioning:" + origPositioning);
+ DOM.setStyleAttribute(contentEl, "position", origPositioning);
+ }
+ // restore scroll position
+ DOM.setElementPropertyInt(contentNode, "scrollTop", scrollTop);
+ DOM.setElementPropertyInt(contentNode, "scrollLeft", scrollLeft);
+
+ } else {
+ DOM.setStyleAttribute(contentNode, "height", "");
+ }
+
+ if (runGeckoFix && BrowserInfo.get().isGecko()) {
+ // workaround for #1764
+ if (width == null || width.equals("")) {
+ if (geckoCaptionMeter == null) {
+ geckoCaptionMeter = DOM.createDiv();
+ DOM.appendChild(captionNode, geckoCaptionMeter);
+ }
+ int captionWidth = DOM.getElementPropertyInt(captionText,
+ "offsetWidth");
+ int availWidth = DOM.getElementPropertyInt(geckoCaptionMeter,
+ "offsetWidth");
+ if (captionWidth == availWidth) {
+ /*
+ * Caption width defines panel width -> Gecko based browsers
+ * somehow fails to float things right, without the
+ * "noncode" below
+ */
+ setWidth(getOffsetWidth() + "px");
+ } else {
+ DOM.setStyleAttribute(captionNode, "width", "");
+ }
+ }
+ }
+ Util.runDescendentsLayout(this);
+ }
+
+ public void onBrowserEvent(Event event) {
+ final Element target = DOM.eventGetTarget(event);
+ final int type = DOM.eventGetType(event);
+ if (type == Event.ONKEYDOWN && shortcutHandler != null) {
+ shortcutHandler.handleKeyboardEvent(event);
+ return;
+ }
+ if (errorIndicatorElement != null
+ && DOM.compare(target, errorIndicatorElement)) {
+ switch (type) {
+ case Event.ONMOUSEOVER:
+ if (errorMessage != null) {
+ errorMessage.showAt(errorIndicatorElement);
+ }
+ break;
+ case Event.ONMOUSEOUT:
+ if (errorMessage != null) {
+ errorMessage.hide();
+ }
+ break;
+ case Event.ONCLICK:
+ ApplicationConnection.getConsole().log(
+ DOM.getInnerHTML(errorMessage.getElement()));
+ return;
+ default:
+ break;
+ }
+ }
+ }
+
+ /**
+ * Panel handles dimensions by itself.
+ */
+ public void setHeight(String height) {
+ // NOP
+ }
+
+ /**
+ * Panel handles dimensions by itself.
+ */
+ public void setWidth(String width) {
+ this.width = width;
+ // Let browser handle 100% width (DIV element takes all size by
+ // default).
+ // This way we can specify borders for Panel's outer element.
+ if (!width.equals("100%")) {
+ super.setWidth(width);
+ }
+ }
+
+}
--- /dev/null
+/* \r
+@ITMillApache2LicenseForJavaFiles@\r
+ */\r
+\r
+package com.itmill.toolkit.terminal.gwt.client.ui;\r
+\r
+import com.google.gwt.user.client.DOM;\r
+\r
+/**\r
+ * This class represents a password field.\r
+ * \r
+ * @author IT Mill Ltd.\r
+ * \r
+ */\r
+public class IPasswordField extends ITextField {\r
+\r
+ public IPasswordField() {\r
+ super(DOM.createInputPassword());\r
+ }\r
+\r
+}\r
--- /dev/null
+/* \r
+@ITMillApache2LicenseForJavaFiles@\r
+ */\r
+\r
+package com.itmill.toolkit.terminal.gwt.client.ui;\r
+\r
+import com.google.gwt.user.client.DOM;\r
+import com.google.gwt.user.client.Timer;\r
+import com.google.gwt.user.client.Window;\r
+import com.google.gwt.user.client.ui.Button;\r
+import com.google.gwt.user.client.ui.ClickListener;\r
+import com.google.gwt.user.client.ui.PopupListener;\r
+import com.google.gwt.user.client.ui.PopupPanel;\r
+import com.google.gwt.user.client.ui.Widget;\r
+import com.google.gwt.user.client.ui.PopupPanel.PositionCallback;\r
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;\r
+import com.itmill.toolkit.terminal.gwt.client.Paintable;\r
+import com.itmill.toolkit.terminal.gwt.client.UIDL;\r
+\r
+public class IPopupCalendar extends ITextualDate implements Paintable, Field,\r
+ ClickListener, PopupListener {\r
+\r
+ private final Button calendarToggle;\r
+\r
+ private final CalendarPanel calendar;\r
+\r
+ private final ToolkitOverlay popup;\r
+ private boolean open = false;\r
+\r
+ public IPopupCalendar() {\r
+ super();\r
+\r
+ calendarToggle = new Button();\r
+ calendarToggle.setStyleName(CLASSNAME + "-button");\r
+ calendarToggle.setText("...");\r
+ calendarToggle.addClickListener(this);\r
+ add(calendarToggle);\r
+\r
+ calendar = new CalendarPanel(this);\r
+ popup = new ToolkitOverlay(true, true);\r
+ popup.setStyleName(IDateField.CLASSNAME + "-popup");\r
+ popup.setWidget(calendar);\r
+ popup.addPopupListener(this);\r
+\r
+ DOM.setElementProperty(calendar.getElement(), "id",\r
+ "PID_TOOLKIT_POPUPCAL");\r
+\r
+ }\r
+\r
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {\r
+ super.updateFromUIDL(uidl, client);\r
+ if (date != null) {\r
+ calendar.updateCalendar();\r
+ }\r
+ calendarToggle.setEnabled(enabled);\r
+ }\r
+\r
+ public void onClick(Widget sender) {\r
+ if (sender == calendarToggle && !open) {\r
+ open = true;\r
+ calendar.updateCalendar();\r
+ // clear previous values\r
+ popup.setWidth("");\r
+ popup.setHeight("");\r
+ popup.setPopupPositionAndShow(new PositionCallback() {\r
+ public void setPosition(int offsetWidth, int offsetHeight) {\r
+ final int w = offsetWidth;\r
+ final int h = offsetHeight;\r
+ int t = calendarToggle.getAbsoluteTop();\r
+ int l = calendarToggle.getAbsoluteLeft();\r
+ if (l + w > Window.getClientWidth()\r
+ + Window.getScrollLeft()) {\r
+ l = Window.getClientWidth() + Window.getScrollLeft()\r
+ - w;\r
+ }\r
+ if (t + h > Window.getClientHeight()\r
+ + Window.getScrollTop()) {\r
+ t = Window.getClientHeight() + Window.getScrollTop()\r
+ - h - calendarToggle.getOffsetHeight() - 30;\r
+ l += calendarToggle.getOffsetWidth();\r
+ }\r
+ popup.setPopupPosition(l, t\r
+ + calendarToggle.getOffsetHeight() + 2);\r
+\r
+ // fix size\r
+ popup.setWidth(w + "px");\r
+ popup.setHeight(h + "px");\r
+ setFocus(true);\r
+ }\r
+ });\r
+ }\r
+ }\r
+\r
+ public void onPopupClosed(PopupPanel sender, boolean autoClosed) {\r
+ if (sender == popup) {\r
+ buildDate();\r
+ // Sigh.\r
+ Timer t = new Timer() {\r
+ public void run() {\r
+ open = false;\r
+ }\r
+ };\r
+ t.schedule(100);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Sets focus to Calendar panel.\r
+ * \r
+ * @param focus\r
+ */\r
+ public void setFocus(boolean focus) {\r
+ calendar.setFocus(focus);\r
+ }\r
+\r
+ protected int getFieldExtraWidth() {\r
+ if (fieldExtraWidth < 0) {\r
+ fieldExtraWidth = super.getFieldExtraWidth();\r
+ fieldExtraWidth += calendarToggle.getOffsetWidth();\r
+ }\r
+ return fieldExtraWidth;\r
+ }\r
+\r
+}\r
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client.ui;
+
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.ui.Widget;
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;
+import com.itmill.toolkit.terminal.gwt.client.Paintable;
+import com.itmill.toolkit.terminal.gwt.client.UIDL;
+
+public class IProgressIndicator extends Widget implements Paintable {
+
+ private static final String CLASSNAME = "i-progressindicator";
+ Element wrapper = DOM.createDiv();
+ Element indicator = DOM.createDiv();
+ private ApplicationConnection client;
+ private final Poller poller;
+ private boolean indeterminate = false;
+
+ public IProgressIndicator() {
+ setElement(wrapper);
+ setStyleName(CLASSNAME);
+ DOM.appendChild(wrapper, indicator);
+ poller = new Poller();
+ }
+
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+ if (client.updateComponent(this, uidl, true)) {
+ return;
+ }
+
+ poller.cancel();
+ this.client = client;
+ if (client.updateComponent(this, uidl, true)) {
+ return;
+ }
+
+ indeterminate = uidl.getBooleanAttribute("indeterminate");
+
+ String style = CLASSNAME;
+ if (uidl.getBooleanAttribute("disabled")) {
+ style += "-disabled";
+ }
+
+ if (indeterminate) {
+ this.setStyleName(style + "-indeterminate");
+ } else {
+ setStyleName(style);
+ try {
+ final float f = Float.parseFloat(uidl
+ .getStringAttribute("state"));
+ final int size = Math.round(100 * f);
+ DOM.setStyleAttribute(indicator, "width", size + "%");
+ } catch (final Exception e) {
+ }
+ }
+
+ if (!uidl.getBooleanAttribute("disabled")) {
+ poller.scheduleRepeating(uidl.getIntAttribute("pollinginterval"));
+ }
+ }
+
+ public void setVisible(boolean visible) {
+ super.setVisible(visible);
+ if (!visible) {
+ poller.cancel();
+ }
+ }
+
+ class Poller extends Timer {
+
+ public void run() {
+ client.sendPendingVariableChanges();
+ }
+
+ }
+
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client.ui;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.Vector;
+
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.DeferredCommand;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.Panel;
+import com.google.gwt.user.client.ui.RootPanel;
+import com.google.gwt.user.client.ui.ScrollListener;
+import com.google.gwt.user.client.ui.ScrollPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;
+import com.itmill.toolkit.terminal.gwt.client.ContainerResizedListener;
+import com.itmill.toolkit.terminal.gwt.client.Paintable;
+import com.itmill.toolkit.terminal.gwt.client.UIDL;
+import com.itmill.toolkit.terminal.gwt.client.Util;
+import com.itmill.toolkit.terminal.gwt.client.ui.IScrollTable.IScrollTableBody.IScrollTableRow;
+
+/**
+ * IScrollTable
+ *
+ * IScrollTable is a FlowPanel having two widgets in it: * TableHead component *
+ * ScrollPanel
+ *
+ * TableHead contains table's header and widgets + logic for resizing,
+ * reordering and hiding columns.
+ *
+ * ScrollPanel contains IScrollTableBody object which handles content. To save
+ * some bandwidth and to improve clients responsiveness with loads of data, in
+ * IScrollTableBody all rows are not necessary rendered. There are "spacers" in
+ * IScrollTableBody to use the exact same space as non-rendered rows would use.
+ * This way we can use seamlessly traditional scrollbars and scrolling to fetch
+ * more rows instead of "paging".
+ *
+ * In IScrollTable we listen to scroll events. On horizontal scrolling we also
+ * update TableHeads scroll position which has its scrollbars hidden. On
+ * vertical scroll events we will check if we are reaching the end of area where
+ * we have rows rendered and
+ *
+ * TODO implement unregistering for child components in Cells
+ */
+public class IScrollTable extends Composite implements Table, ScrollListener,
+ ContainerResizedListener {
+
+ public static final String CLASSNAME = "i-table";
+ /**
+ * multiple of pagelenght which component will cache when requesting more
+ * rows
+ */
+ private static final double CACHE_RATE = 2;
+ /**
+ * fraction of pageLenght which can be scrolled without making new request
+ */
+ private static final double CACHE_REACT_RATE = 1.5;
+
+ public static final char ALIGN_CENTER = 'c';
+ public static final char ALIGN_LEFT = 'b';
+ public static final char ALIGN_RIGHT = 'e';
+ private int firstRowInViewPort = 0;
+ private int pageLength = 15;
+
+ private boolean showRowHeaders = false;
+
+ private String[] columnOrder;
+
+ private ApplicationConnection client;
+ private String paintableId;
+
+ private boolean immediate;
+
+ private int selectMode = Table.SELECT_MODE_NONE;
+
+ private final HashSet selectedRowKeys = new HashSet();
+
+ private boolean initializedAndAttached = false;
+
+ private final TableHead tHead = new TableHead();
+
+ private final ScrollPanel bodyContainer = new ScrollPanel();
+
+ private int totalRows;
+
+ private Set collapsedColumns;
+
+ private final RowRequestHandler rowRequestHandler;
+ private IScrollTableBody tBody;
+ private String width;
+ private String height;
+ private int firstvisible = 0;
+ private boolean sortAscending;
+ private String sortColumn;
+ private boolean columnReordering;
+
+ /**
+ * This map contains captions and icon urls for actions like: * "33_c" ->
+ * "Edit" * "33_i" -> "http://dom.com/edit.png"
+ */
+ private final HashMap actionMap = new HashMap();
+ private String[] visibleColOrder;
+ private boolean initialContentReceived = false;
+ private Element scrollPositionElement;
+ private final FlowPanel panel;
+ private boolean enabled;
+ private boolean showColHeaders;
+
+ /** flag to indicate that table body has changed */
+ private boolean isNewBody = true;
+
+ /**
+ * Stores old height for IE, that sometimes fails to return correct height
+ * for container element. Then this value is used as a fallback.
+ */
+ private int oldAvailPixels;
+
+ public IScrollTable() {
+
+ bodyContainer.addScrollListener(this);
+ bodyContainer.setStyleName(CLASSNAME + "-body");
+
+ panel = new FlowPanel();
+ panel.setStyleName(CLASSNAME);
+ panel.add(tHead);
+ panel.add(bodyContainer);
+
+ rowRequestHandler = new RowRequestHandler();
+
+ initWidget(panel);
+ }
+
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+ if (client.updateComponent(this, uidl, true)) {
+ return;
+ }
+
+ enabled = !uidl.hasAttribute("disabled");
+
+ this.client = client;
+ paintableId = uidl.getStringAttribute("id");
+ immediate = uidl.getBooleanAttribute("immediate");
+ final int newTotalRows = uidl.getIntAttribute("totalrows");
+ if (newTotalRows != totalRows) {
+ totalRows = newTotalRows;
+ if (tBody != null) {
+ initializedAndAttached = false;
+ initialContentReceived = false;
+ isNewBody = true;
+ }
+ }
+
+ pageLength = uidl.getIntAttribute("pagelength");
+ if (pageLength == 0) {
+ pageLength = totalRows;
+ }
+ firstvisible = uidl.hasVariable("firstvisible") ? uidl
+ .getIntVariable("firstvisible") : 0;
+
+ showRowHeaders = uidl.getBooleanAttribute("rowheaders");
+ showColHeaders = uidl.getBooleanAttribute("colheaders");
+
+ if (uidl.hasAttribute("width")) {
+ width = uidl.getStringAttribute("width");
+ }
+ if (uidl.hasAttribute("height")) {
+ height = uidl.getStringAttribute("height");
+ }
+
+ if (uidl.hasVariable("sortascending")) {
+ sortAscending = uidl.getBooleanVariable("sortascending");
+ sortColumn = uidl.getStringVariable("sortcolumn");
+ }
+
+ if (uidl.hasVariable("selected")) {
+ final Set selectedKeys = uidl
+ .getStringArrayVariableAsSet("selected");
+ selectedRowKeys.clear();
+ for (final Iterator it = selectedKeys.iterator(); it.hasNext();) {
+ selectedRowKeys.add(it.next());
+ }
+ }
+
+ if (uidl.hasAttribute("selectmode")) {
+ if (uidl.getBooleanAttribute("readonly")) {
+ selectMode = Table.SELECT_MODE_NONE;
+ } else if (uidl.getStringAttribute("selectmode").equals("multi")) {
+ selectMode = Table.SELECT_MODE_MULTI;
+ } else if (uidl.getStringAttribute("selectmode").equals("single")) {
+ selectMode = Table.SELECT_MODE_SINGLE;
+ } else {
+ selectMode = Table.SELECT_MODE_NONE;
+ }
+ }
+
+ if (uidl.hasVariable("columnorder")) {
+ columnReordering = true;
+ columnOrder = uidl.getStringArrayVariable("columnorder");
+ }
+
+ if (uidl.hasVariable("collapsedcolumns")) {
+ tHead.setColumnCollapsingAllowed(true);
+ collapsedColumns = uidl
+ .getStringArrayVariableAsSet("collapsedcolumns");
+ } else {
+ tHead.setColumnCollapsingAllowed(false);
+ }
+
+ UIDL rowData = null;
+ for (final Iterator it = uidl.getChildIterator(); it.hasNext();) {
+ final UIDL c = (UIDL) it.next();
+ if (c.getTag().equals("rows")) {
+ rowData = c;
+ } else if (c.getTag().equals("actions")) {
+ updateActionMap(c);
+ } else if (c.getTag().equals("visiblecolumns")) {
+ tHead.updateCellsFromUIDL(c);
+ }
+ }
+ updateHeader(uidl.getStringArrayAttribute("vcolorder"));
+
+ if (initializedAndAttached) {
+ updateBody(rowData, uidl.getIntAttribute("firstrow"), uidl
+ .getIntAttribute("rows"));
+ } else {
+ if (tBody != null) {
+ tBody.removeFromParent();
+ client.unregisterChildPaintables(tBody);
+ }
+ tBody = new IScrollTableBody();
+
+ tBody.renderInitialRows(rowData, uidl.getIntAttribute("firstrow"),
+ uidl.getIntAttribute("rows"));
+ bodyContainer.add(tBody);
+ initialContentReceived = true;
+ if (isAttached()) {
+ sizeInit();
+ }
+ }
+ hideScrollPositionAnnotation();
+ }
+
+ private void updateActionMap(UIDL c) {
+ final Iterator it = c.getChildIterator();
+ while (it.hasNext()) {
+ final UIDL action = (UIDL) it.next();
+ final String key = action.getStringAttribute("key");
+ final String caption = action.getStringAttribute("caption");
+ actionMap.put(key + "_c", caption);
+ if (action.hasAttribute("icon")) {
+ // TODO need some uri handling ??
+ actionMap.put(key + "_i", client.translateToolkitUri(action
+ .getStringAttribute("icon")));
+ }
+ }
+
+ }
+
+ public String getActionCaption(String actionKey) {
+ return (String) actionMap.get(actionKey + "_c");
+ }
+
+ public String getActionIcon(String actionKey) {
+ return (String) actionMap.get(actionKey + "_i");
+ }
+
+ private void updateHeader(String[] strings) {
+ if (strings == null) {
+ return;
+ }
+
+ int visibleCols = strings.length;
+ int colIndex = 0;
+ if (showRowHeaders) {
+ tHead.enableColumn("0", colIndex);
+ visibleCols++;
+ visibleColOrder = new String[visibleCols];
+ visibleColOrder[colIndex] = "0";
+ colIndex++;
+ } else {
+ visibleColOrder = new String[visibleCols];
+ tHead.removeCell("0");
+ }
+
+ int i;
+ for (i = 0; i < strings.length; i++) {
+ final String cid = strings[i];
+ visibleColOrder[colIndex] = cid;
+ tHead.enableColumn(cid, colIndex);
+ colIndex++;
+ }
+
+ tHead.setVisible(showColHeaders);
+
+ }
+
+ /**
+ * @param uidl
+ * which contains row data
+ * @param firstRow
+ * first row in data set
+ * @param reqRows
+ * amount of rows in data set
+ */
+ private void updateBody(UIDL uidl, int firstRow, int reqRows) {
+ if (uidl == null || reqRows < 1) {
+ // container is empty, remove possibly existing rows
+ if (firstRow < 0) {
+ while (tBody.getLastRendered() > tBody.firstRendered) {
+ tBody.unlinkRow(false);
+ }
+ tBody.unlinkRow(false);
+ }
+ return;
+ }
+
+ tBody.renderRows(uidl, firstRow, reqRows);
+
+ final int optimalFirstRow = (int) (firstRowInViewPort - pageLength
+ * CACHE_RATE);
+ boolean cont = true;
+ while (cont && tBody.getLastRendered() > optimalFirstRow
+ && tBody.getFirstRendered() < optimalFirstRow) {
+ // client.console.log("removing row from start");
+ cont = tBody.unlinkRow(true);
+ }
+ final int optimalLastRow = (int) (firstRowInViewPort + pageLength + pageLength
+ * CACHE_RATE);
+ cont = true;
+ while (cont && tBody.getLastRendered() > optimalLastRow) {
+ // client.console.log("removing row from the end");
+ cont = tBody.unlinkRow(false);
+ }
+ tBody.fixSpacers();
+
+ }
+
+ /**
+ * Gives correct column index for given column key ("cid" in UIDL).
+ *
+ * @param colKey
+ * @return column index of visible columns, -1 if column not visible
+ */
+ private int getColIndexByKey(String colKey) {
+ // return 0 if asked for rowHeaders
+ if ("0".equals(colKey)) {
+ return 0;
+ }
+ for (int i = 0; i < visibleColOrder.length; i++) {
+ if (visibleColOrder[i].equals(colKey)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ private boolean isCollapsedColumn(String colKey) {
+ if (collapsedColumns == null) {
+ return false;
+ }
+ if (collapsedColumns.contains(colKey)) {
+ return true;
+ }
+ return false;
+ }
+
+ private String getColKeyByIndex(int index) {
+ return tHead.getHeaderCell(index).getColKey();
+ }
+
+ private void setColWidth(int colIndex, int w) {
+ final HeaderCell cell = tHead.getHeaderCell(colIndex);
+ cell.setWidth(w);
+ tBody.setColWidth(colIndex, w);
+ }
+
+ private int getColWidth(String colKey) {
+ return tHead.getHeaderCell(colKey).getWidth();
+ }
+
+ private IScrollTableRow getRenderedRowByKey(String key) {
+ final Iterator it = tBody.iterator();
+ IScrollTableRow r = null;
+ while (it.hasNext()) {
+ r = (IScrollTableRow) it.next();
+ if (r.getKey().equals(key)) {
+ return r;
+ }
+ }
+ return null;
+ }
+
+ private void reOrderColumn(String columnKey, int newIndex) {
+
+ final int oldIndex = getColIndexByKey(columnKey);
+
+ // Change header order
+ tHead.moveCell(oldIndex, newIndex);
+
+ // Change body order
+ tBody.moveCol(oldIndex, newIndex);
+
+ /*
+ * Build new columnOrder and update it to server Note that columnOrder
+ * also contains collapsed columns so we cannot directly build it from
+ * cells vector Loop the old columnOrder and append in order to new
+ * array unless on moved columnKey. On new index also put the moved key
+ * i == index on columnOrder, j == index on newOrder
+ */
+ final String oldKeyOnNewIndex = visibleColOrder[newIndex];
+ if (showRowHeaders) {
+ newIndex--; // columnOrder don't have rowHeader
+ }
+ // add back hidden rows,
+ for (int i = 0; i < columnOrder.length; i++) {
+ if (columnOrder[i].equals(oldKeyOnNewIndex)) {
+ break; // break loop at target
+ }
+ if (isCollapsedColumn(columnOrder[i])) {
+ newIndex++;
+ }
+ }
+ // finally we can build the new columnOrder for server
+ final String[] newOrder = new String[columnOrder.length];
+ for (int i = 0, j = 0; j < newOrder.length; i++) {
+ if (j == newIndex) {
+ newOrder[j] = columnKey;
+ j++;
+ }
+ if (i == columnOrder.length) {
+ break;
+ }
+ if (columnOrder[i].equals(columnKey)) {
+ continue;
+ }
+ newOrder[j] = columnOrder[i];
+ j++;
+ }
+ columnOrder = newOrder;
+ // also update visibleColumnOrder
+ int i = showRowHeaders ? 1 : 0;
+ for (int j = 0; j < newOrder.length; j++) {
+ final String cid = newOrder[j];
+ if (!isCollapsedColumn(cid)) {
+ visibleColOrder[i++] = cid;
+ }
+ }
+ client.updateVariable(paintableId, "columnorder", columnOrder, false);
+ }
+
+ protected void onAttach() {
+ super.onAttach();
+ if (initialContentReceived) {
+ sizeInit();
+ }
+ }
+
+ protected void onDetach() {
+ rowRequestHandler.cancel();
+ super.onDetach();
+ // ensure that scrollPosElement will be detached
+ if (scrollPositionElement != null) {
+ final Element parent = DOM.getParent(scrollPositionElement);
+ if (parent != null) {
+ DOM.removeChild(parent, scrollPositionElement);
+ }
+ }
+ }
+
+ /**
+ * Run only once when component is attached and received its initial
+ * content. This function : * Syncs headers and bodys "natural widths and
+ * saves the values. * Sets proper width and height * Makes deferred request
+ * to get some cache rows
+ */
+ private void sizeInit() {
+ /*
+ * We will use browsers table rendering algorithm to find proper column
+ * widths. If content and header take less space than available, we will
+ * divide extra space relatively to each column which has not width set.
+ *
+ * Overflow pixels are added to last column.
+ *
+ */
+
+ Iterator headCells = tHead.iterator();
+ int i = 0;
+ int totalExplicitColumnsWidths = 0;
+ int total = 0;
+
+ final int[] widths = new int[tHead.visibleCells.size()];
+
+ if (width == null) {
+ // if this is a re-init, remove old manually fixed size
+ bodyContainer.setWidth("");
+ tHead.setWidth("");
+ super.setWidth("");
+ }
+
+ tHead.enableBrowserIntelligence();
+ // first loop: collect natural widths
+ while (headCells.hasNext()) {
+ final HeaderCell hCell = (HeaderCell) headCells.next();
+ int w = hCell.getWidth();
+ if (w > 0) {
+ // server has defined column width explicitly
+ totalExplicitColumnsWidths += w;
+ } else {
+ final int hw = hCell.getOffsetWidth();
+ final int cw = tBody.getColWidth(i);
+ w = (hw > cw ? hw : cw) + IScrollTableBody.CELL_EXTRA_WIDTH;
+ }
+ widths[i] = w;
+ total += w;
+ i++;
+ }
+
+ tHead.disableBrowserIntelligence();
+
+ if (height == null) {
+ bodyContainer.setHeight((tBody.getRowHeight() * pageLength) + "px");
+ } else {
+ mySetHeight(height);
+ iLayout();
+ }
+
+ if (width == null) {
+ int w = total;
+ w += getScrollbarWidth();
+ bodyContainer.setWidth(w + "px");
+ tHead.setWidth(w + "px");
+ super.setWidth(w + "px");
+ } else {
+ if (width.indexOf("px") > 0) {
+ bodyContainer.setWidth(width);
+ tHead.setWidth(width);
+ super.setWidth(width);
+ } else if (width.indexOf("%") > 0) {
+ if (!width.equals("100%")) {
+ super.setWidth(width);
+ }
+ // contained blocks are relatively to container element
+ bodyContainer.setWidth("100%");
+ tHead.setWidth("100%");
+
+ }
+ }
+
+ int availW = tBody.getAvailableWidth();
+ // Hey IE, are you really sure about this?
+ availW = tBody.getAvailableWidth();
+
+ if (availW > total) {
+ // natural size is smaller than available space
+ final int extraSpace = availW - total;
+ final int totalWidthR = total - totalExplicitColumnsWidths;
+ if (totalWidthR > 0) {
+ // now we will share this sum relatively to those without
+ // explicit width
+ headCells = tHead.iterator();
+ i = 0;
+ HeaderCell hCell;
+ while (headCells.hasNext()) {
+ hCell = (HeaderCell) headCells.next();
+ if (hCell.getWidth() == -1) {
+ int w = widths[i];
+ final int newSpace = extraSpace * w / totalWidthR;
+ w += newSpace;
+ widths[i] = w;
+ }
+ i++;
+ }
+ }
+ } else {
+ // bodys size will be more than available and scrollbar will appear
+ }
+
+ // last loop: set possibly modified values or reset if new tBody
+ i = 0;
+ headCells = tHead.iterator();
+ while (headCells.hasNext()) {
+ final HeaderCell hCell = (HeaderCell) headCells.next();
+ if (isNewBody || hCell.getWidth() == -1) {
+ final int w = widths[i];
+ setColWidth(i, w);
+ }
+ i++;
+ }
+
+ isNewBody = false;
+
+ if (firstvisible > 0) {
+ // Deferred due some Firefox oddities. IE & Safari could survive
+ // without
+ DeferredCommand.addCommand(new Command() {
+ public void execute() {
+ bodyContainer.setScrollPosition(firstvisible
+ * tBody.getRowHeight());
+ firstRowInViewPort = firstvisible;
+ }
+ });
+ }
+
+ if (enabled) {
+ // Do we need cache rows
+ if (tBody.getLastRendered() + 1 < firstRowInViewPort + pageLength
+ + CACHE_REACT_RATE * pageLength) {
+ DeferredCommand.addCommand(new Command() {
+ public void execute() {
+ if (totalRows - 1 > tBody.getLastRendered()) {
+ // fetch cache rows
+ rowRequestHandler.setReqFirstRow(tBody
+ .getLastRendered() + 1);
+ rowRequestHandler
+ .setReqRows((int) (pageLength * CACHE_RATE));
+ rowRequestHandler.deferRowFetch(1);
+ }
+ }
+ });
+ }
+ }
+ initializedAndAttached = true;
+ }
+
+ public void iLayout() {
+ if (height != null) {
+ if (height.equals("100%")) {
+ /*
+ * We define height in pixels with 100% not to include borders
+ * which is what users usually want. So recalculate pixels via
+ * setHeight.
+ */
+ mySetHeight(height);
+ }
+
+ int contentH = (DOM.getElementPropertyInt(getElement(),
+ "clientHeight") - tHead.getOffsetHeight());
+ if (contentH < 0) {
+ contentH = 0;
+ }
+ bodyContainer.setHeight(contentH + "px");
+ }
+ }
+
+ private int getScrollbarWidth() {
+ return bodyContainer.getOffsetWidth()
+ - DOM.getElementPropertyInt(bodyContainer.getElement(),
+ "clientWidth");
+ }
+
+ /**
+ * This method has logic which rows needs to be requested from server when
+ * user scrolls
+ */
+ public void onScroll(Widget widget, int scrollLeft, int scrollTop) {
+ if (!initializedAndAttached) {
+ return;
+ }
+ if (!enabled) {
+ bodyContainer.setScrollPosition(firstRowInViewPort
+ * tBody.getRowHeight());
+ return;
+ }
+
+ rowRequestHandler.cancel();
+
+ // fix headers horizontal scrolling
+ tHead.setHorizontalScrollPosition(scrollLeft);
+
+ firstRowInViewPort = (int) Math.ceil(scrollTop
+ / (double) tBody.getRowHeight());
+ // ApplicationConnection.getConsole().log(
+ // "At scrolltop: " + scrollTop + " At row " + firstRowInViewPort);
+
+ int postLimit = (int) (firstRowInViewPort + pageLength + pageLength
+ * CACHE_REACT_RATE);
+ if (postLimit > totalRows - 1) {
+ postLimit = totalRows - 1;
+ }
+ int preLimit = (int) (firstRowInViewPort - pageLength
+ * CACHE_REACT_RATE);
+ if (preLimit < 0) {
+ preLimit = 0;
+ }
+ final int lastRendered = tBody.getLastRendered();
+ final int firstRendered = tBody.getFirstRendered();
+
+ if (postLimit <= lastRendered && preLimit >= firstRendered) {
+ client.updateVariable(paintableId, "firstvisible",
+ firstRowInViewPort, false);
+ return; // scrolled withing "non-react area"
+ }
+
+ if (firstRowInViewPort - pageLength * CACHE_RATE > lastRendered
+ || firstRowInViewPort + pageLength + pageLength * CACHE_RATE < firstRendered) {
+ // need a totally new set
+ // ApplicationConnection.getConsole().log(
+ // "Table: need a totally new set");
+ rowRequestHandler
+ .setReqFirstRow((int) (firstRowInViewPort - pageLength
+ * CACHE_RATE));
+ int last = firstRowInViewPort + (int) CACHE_RATE * pageLength
+ + pageLength;
+ if (last > totalRows) {
+ last = totalRows - 1;
+ }
+ rowRequestHandler.setReqRows(last
+ - rowRequestHandler.getReqFirstRow() + 1);
+ rowRequestHandler.deferRowFetch();
+ return;
+ }
+ if (preLimit < firstRendered) {
+ // need some rows to the beginning of the rendered area
+ // ApplicationConnection
+ // .getConsole()
+ // .log(
+ // "Table: need some rows to the beginning of the rendered area");
+ rowRequestHandler
+ .setReqFirstRow((int) (firstRowInViewPort - pageLength
+ * CACHE_RATE));
+ rowRequestHandler.setReqRows(firstRendered
+ - rowRequestHandler.getReqFirstRow());
+ rowRequestHandler.deferRowFetch();
+
+ return;
+ }
+ if (postLimit > lastRendered) {
+ // need some rows to the end of the rendered area
+ // ApplicationConnection.getConsole().log(
+ // "need some rows to the end of the rendered area");
+ rowRequestHandler.setReqFirstRow(lastRendered + 1);
+ rowRequestHandler.setReqRows((int) ((firstRowInViewPort
+ + pageLength + pageLength * CACHE_RATE) - lastRendered));
+ rowRequestHandler.deferRowFetch();
+ }
+
+ }
+
+ private void announceScrollPosition() {
+ if (scrollPositionElement == null) {
+ scrollPositionElement = DOM.createDiv();
+ DOM.setElementProperty(scrollPositionElement, "className",
+ "i-table-scrollposition");
+ DOM.appendChild(getElement(), scrollPositionElement);
+ }
+
+ DOM.setStyleAttribute(scrollPositionElement, "position", "absolute");
+ DOM.setStyleAttribute(scrollPositionElement, "marginLeft", (DOM
+ .getElementPropertyInt(getElement(), "offsetWidth") / 2 - 80)
+ + "px");
+ DOM.setStyleAttribute(scrollPositionElement, "marginTop", -(DOM
+ .getElementPropertyInt(getElement(), "offsetHeight") - 2)
+ + "px");
+
+ // indexes go from 1-totalRows, as rowheaders in index-mode indicate
+ int last = (firstRowInViewPort + (bodyContainer.getOffsetHeight() / tBody
+ .getRowHeight()));
+ if (last > totalRows) {
+ last = totalRows;
+ }
+ DOM.setInnerHTML(scrollPositionElement, "<span>"
+ + (firstRowInViewPort + 1) + " – " + last + "..."
+ + "</span>");
+ DOM.setStyleAttribute(scrollPositionElement, "display", "block");
+ }
+
+ private void hideScrollPositionAnnotation() {
+ if (scrollPositionElement != null) {
+ DOM.setStyleAttribute(scrollPositionElement, "display", "none");
+ }
+ }
+
+ private class RowRequestHandler extends Timer {
+
+ private int reqFirstRow = 0;
+ private int reqRows = 0;
+
+ public void deferRowFetch() {
+ deferRowFetch(250);
+ }
+
+ public void deferRowFetch(int msec) {
+ if (reqRows > 0 && reqFirstRow < totalRows) {
+ schedule(msec);
+
+ // tell scroll position to user if currently "visible" rows are
+ // not rendered
+ if ((firstRowInViewPort + pageLength > tBody.getLastRendered())
+ || (firstRowInViewPort < tBody.getFirstRendered())) {
+ announceScrollPosition();
+ } else {
+ hideScrollPositionAnnotation();
+ }
+ }
+ }
+
+ public void setReqFirstRow(int reqFirstRow) {
+ if (reqFirstRow < 0) {
+ reqFirstRow = 0;
+ } else if (reqFirstRow >= totalRows) {
+ reqFirstRow = totalRows - 1;
+ }
+ this.reqFirstRow = reqFirstRow;
+ }
+
+ public void setReqRows(int reqRows) {
+ this.reqRows = reqRows;
+ }
+
+ public void run() {
+ ApplicationConnection.getConsole().log(
+ "Getting " + reqRows + " rows from " + reqFirstRow);
+
+ int firstToBeRendered = tBody.firstRendered;
+ if (reqFirstRow < firstToBeRendered) {
+ firstToBeRendered = reqFirstRow;
+ } else if (firstRowInViewPort - (int) (CACHE_RATE * pageLength) > firstToBeRendered) {
+ firstToBeRendered = firstRowInViewPort
+ - (int) (CACHE_RATE * pageLength);
+ if (firstToBeRendered < 0) {
+ firstToBeRendered = 0;
+ }
+ }
+
+ int lastToBeRendered = tBody.lastRendered;
+
+ if (reqFirstRow + reqRows - 1 > lastToBeRendered) {
+ lastToBeRendered = reqFirstRow + reqRows - 1;
+ } else if (firstRowInViewPort + pageLength + pageLength
+ * CACHE_RATE < lastToBeRendered) {
+ lastToBeRendered = (firstRowInViewPort + pageLength + (int) (pageLength * CACHE_RATE));
+ if (lastToBeRendered >= totalRows) {
+ lastToBeRendered = totalRows - 1;
+ }
+ }
+
+ client.updateVariable(paintableId, "firstToBeRendered",
+ firstToBeRendered, false);
+
+ client.updateVariable(paintableId, "lastToBeRendered",
+ lastToBeRendered, false);
+
+ client.updateVariable(paintableId, "firstvisible",
+ firstRowInViewPort, false);
+ client.updateVariable(paintableId, "reqfirstrow", reqFirstRow,
+ false);
+ client.updateVariable(paintableId, "reqrows", reqRows, true);
+ }
+
+ public int getReqFirstRow() {
+ return reqFirstRow;
+ }
+
+ public int getReqRows() {
+ return reqRows;
+ }
+
+ /**
+ * Sends request to refresh content at this position.
+ */
+ public void refreshContent() {
+ int first = (int) (firstRowInViewPort - pageLength * CACHE_RATE);
+ int reqRows = (int) (2 * pageLength * CACHE_RATE + pageLength);
+ if (first < 0) {
+ reqRows = reqRows + first;
+ first = 0;
+ }
+ setReqFirstRow(first);
+ setReqRows(reqRows);
+ run();
+ }
+ }
+
+ public class HeaderCell extends Widget {
+
+ private static final int DRAG_WIDGET_WIDTH = 4;
+
+ private static final int MINIMUM_COL_WIDTH = 20;
+
+ Element td = DOM.createTD();
+
+ Element captionContainer = DOM.createDiv();
+
+ Element colResizeWidget = DOM.createDiv();
+
+ Element floatingCopyOfHeaderCell;
+
+ private boolean sortable = false;
+ private final String cid;
+ private boolean dragging;
+
+ private int dragStartX;
+ private int colIndex;
+ private int originalWidth;
+
+ private boolean isResizing;
+
+ private int headerX;
+
+ private boolean moved;
+
+ private int closestSlot;
+
+ private int width = -1;
+
+ private char align = ALIGN_LEFT;
+
+ public void setSortable(boolean b) {
+ sortable = b;
+ }
+
+ public HeaderCell(String colId, String headerText) {
+ cid = colId;
+
+ DOM.setElementProperty(colResizeWidget, "className", CLASSNAME
+ + "-resizer");
+ DOM.setStyleAttribute(colResizeWidget, "width", DRAG_WIDGET_WIDTH
+ + "px");
+ DOM.sinkEvents(colResizeWidget, Event.MOUSEEVENTS);
+
+ setText(headerText);
+
+ DOM.appendChild(td, colResizeWidget);
+
+ DOM.setElementProperty(captionContainer, "className", CLASSNAME
+ + "-caption-container");
+
+ // ensure no clipping initially (problem on column additions)
+ DOM.setStyleAttribute(captionContainer, "overflow", "visible");
+
+ DOM.sinkEvents(captionContainer, Event.MOUSEEVENTS);
+
+ DOM.appendChild(td, captionContainer);
+
+ DOM.sinkEvents(td, Event.MOUSEEVENTS);
+
+ setElement(td);
+ }
+
+ public void setWidth(int w) {
+ if (width == -1) {
+ // go to default mode, clip content if necessary
+ DOM.setStyleAttribute(captionContainer, "overflow", "");
+ }
+ width = w;
+ DOM.setStyleAttribute(captionContainer, "width", (w
+ - DRAG_WIDGET_WIDTH - 4)
+ + "px");
+ setWidth(w + "px");
+ }
+
+ public int getWidth() {
+ return width;
+ }
+
+ public void setText(String headerText) {
+ DOM.setInnerHTML(captionContainer, headerText);
+ }
+
+ public String getColKey() {
+ return cid;
+ }
+
+ private void setSorted(boolean sorted) {
+ if (sorted) {
+ if (sortAscending) {
+ this.setStyleName(CLASSNAME + "-header-cell-asc");
+ } else {
+ this.setStyleName(CLASSNAME + "-header-cell-desc");
+ }
+ } else {
+ this.setStyleName(CLASSNAME + "-header-cell");
+ }
+ }
+
+ /**
+ * Handle column reordering.
+ */
+ public void onBrowserEvent(Event event) {
+ if (enabled) {
+ if (isResizing
+ || DOM.compare(DOM.eventGetTarget(event),
+ colResizeWidget)) {
+ onResizeEvent(event);
+ } else {
+ handleCaptionEvent(event);
+ }
+ }
+ }
+
+ private void createFloatingCopy() {
+ floatingCopyOfHeaderCell = DOM.createDiv();
+ DOM.setInnerHTML(floatingCopyOfHeaderCell, DOM.getInnerHTML(td));
+ floatingCopyOfHeaderCell = DOM
+ .getChild(floatingCopyOfHeaderCell, 1);
+ DOM.setElementProperty(floatingCopyOfHeaderCell, "className",
+ CLASSNAME + "-header-drag");
+ updateFloatingCopysPosition(DOM.getAbsoluteLeft(td), DOM
+ .getAbsoluteTop(td));
+ DOM.appendChild(RootPanel.get().getElement(),
+ floatingCopyOfHeaderCell);
+ }
+
+ private void updateFloatingCopysPosition(int x, int y) {
+ x -= DOM.getElementPropertyInt(floatingCopyOfHeaderCell,
+ "offsetWidth") / 2;
+ DOM.setStyleAttribute(floatingCopyOfHeaderCell, "left", x + "px");
+ if (y > 0) {
+ DOM.setStyleAttribute(floatingCopyOfHeaderCell, "top", (y + 7)
+ + "px");
+ }
+ }
+
+ private void hideFloatingCopy() {
+ DOM.removeChild(RootPanel.get().getElement(),
+ floatingCopyOfHeaderCell);
+ floatingCopyOfHeaderCell = null;
+ }
+
+ protected void handleCaptionEvent(Event event) {
+ switch (DOM.eventGetType(event)) {
+ case Event.ONMOUSEDOWN:
+ ApplicationConnection.getConsole().log(
+ "HeaderCaption: mouse down");
+ if (columnReordering) {
+ dragging = true;
+ moved = false;
+ colIndex = getColIndexByKey(cid);
+ DOM.setCapture(getElement());
+ headerX = tHead.getAbsoluteLeft();
+ ApplicationConnection
+ .getConsole()
+ .log(
+ "HeaderCaption: Caption set to capture mouse events");
+ DOM.eventPreventDefault(event); // prevent selecting text
+ }
+ break;
+ case Event.ONMOUSEUP:
+ ApplicationConnection.getConsole()
+ .log("HeaderCaption: mouseUP");
+ if (columnReordering) {
+ dragging = false;
+ DOM.releaseCapture(getElement());
+ ApplicationConnection.getConsole().log(
+ "HeaderCaption: Stopped column reordering");
+ if (moved) {
+ hideFloatingCopy();
+ tHead.removeSlotFocus();
+ if (closestSlot != colIndex
+ && closestSlot != (colIndex + 1)) {
+ if (closestSlot > colIndex) {
+ reOrderColumn(cid, closestSlot - 1);
+ } else {
+ reOrderColumn(cid, closestSlot);
+ }
+ }
+ }
+ }
+
+ if (!moved) {
+ // mouse event was a click to header -> sort column
+ if (sortable) {
+ if (sortColumn.equals(cid)) {
+ // just toggle order
+ client.updateVariable(paintableId, "sortascending",
+ !sortAscending, false);
+ } else {
+ // set table scrolled by this column
+ client.updateVariable(paintableId, "sortcolumn",
+ cid, false);
+ }
+ // get also cache columns at the same request
+ bodyContainer.setScrollPosition(0);
+ firstvisible = 0;
+ rowRequestHandler.setReqFirstRow(0);
+ rowRequestHandler.setReqRows((int) (2 * pageLength
+ * CACHE_RATE + pageLength));
+ rowRequestHandler.deferRowFetch();
+ }
+ break;
+ }
+ break;
+ case Event.ONMOUSEMOVE:
+ if (dragging) {
+ ApplicationConnection.getConsole().log(
+ "HeaderCaption: Dragging column, optimal index...");
+ if (!moved) {
+ createFloatingCopy();
+ moved = true;
+ }
+ final int x = DOM.eventGetClientX(event)
+ + DOM.getElementPropertyInt(tHead.hTableWrapper,
+ "scrollLeft");
+ int slotX = headerX;
+ closestSlot = colIndex;
+ int closestDistance = -1;
+ int start = 0;
+ if (showRowHeaders) {
+ start++;
+ }
+ final int visibleCellCount = tHead.getVisibleCellCount();
+ for (int i = start; i <= visibleCellCount; i++) {
+ if (i > 0) {
+ final String colKey = getColKeyByIndex(i - 1);
+ slotX += getColWidth(colKey);
+ }
+ final int dist = Math.abs(x - slotX);
+ if (closestDistance == -1 || dist < closestDistance) {
+ closestDistance = dist;
+ closestSlot = i;
+ }
+ }
+ tHead.focusSlot(closestSlot);
+
+ updateFloatingCopysPosition(DOM.eventGetClientX(event), -1);
+ ApplicationConnection.getConsole().log("" + closestSlot);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ private void onResizeEvent(Event event) {
+ switch (DOM.eventGetType(event)) {
+ case Event.ONMOUSEDOWN:
+ isResizing = true;
+ DOM.setCapture(getElement());
+ dragStartX = DOM.eventGetClientX(event);
+ colIndex = getColIndexByKey(cid);
+ originalWidth = getWidth();
+ DOM.eventPreventDefault(event);
+ break;
+ case Event.ONMOUSEUP:
+ isResizing = false;
+ DOM.releaseCapture(getElement());
+ break;
+ case Event.ONMOUSEMOVE:
+ if (isResizing) {
+ final int deltaX = DOM.eventGetClientX(event) - dragStartX;
+ if (deltaX == 0) {
+ return;
+ }
+
+ int newWidth = originalWidth + deltaX;
+ if (newWidth < MINIMUM_COL_WIDTH) {
+ newWidth = MINIMUM_COL_WIDTH;
+ }
+ setColWidth(colIndex, newWidth);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ public String getCaption() {
+ return DOM.getInnerText(captionContainer);
+ }
+
+ public boolean isEnabled() {
+ return getParent() != null;
+ }
+
+ public void setAlign(char c) {
+ if (align != c) {
+ switch (c) {
+ case ALIGN_CENTER:
+ DOM.setStyleAttribute(captionContainer, "textAlign",
+ "center");
+ break;
+ case ALIGN_RIGHT:
+ DOM.setStyleAttribute(captionContainer, "textAlign",
+ "right");
+ break;
+ default:
+ DOM.setStyleAttribute(captionContainer, "textAlign", "");
+ break;
+ }
+ }
+ align = c;
+ }
+
+ public char getAlign() {
+ return align;
+ }
+
+ }
+
+ /**
+ * HeaderCell that is header cell for row headers.
+ *
+ * Reordering disabled and clicking on it resets sorting.
+ */
+ public class RowHeadersHeaderCell extends HeaderCell {
+
+ RowHeadersHeaderCell() {
+ super("0", "");
+ }
+
+ protected void handleCaptionEvent(Event event) {
+ // NOP: RowHeaders cannot be reordered
+ // TODO It'd be nice to reset sorting here
+ }
+ }
+
+ public class TableHead extends Panel implements ActionOwner {
+
+ private static final int WRAPPER_WIDTH = 9000;
+
+ Vector visibleCells = new Vector();
+
+ HashMap availableCells = new HashMap();
+
+ Element div = DOM.createDiv();
+ Element hTableWrapper = DOM.createDiv();
+ Element hTableContainer = DOM.createDiv();
+ Element table = DOM.createTable();
+ Element headerTableBody = DOM.createTBody();
+ Element tr = DOM.createTR();
+
+ private final Element columnSelector = DOM.createDiv();
+
+ private int focusedSlot = -1;
+
+ public TableHead() {
+ DOM.setStyleAttribute(hTableWrapper, "overflow", "hidden");
+ DOM.setElementProperty(hTableWrapper, "className", CLASSNAME
+ + "-header");
+
+ // TODO move styles to CSS
+ DOM.setElementProperty(columnSelector, "className", CLASSNAME
+ + "-column-selector");
+ DOM.setStyleAttribute(columnSelector, "display", "none");
+
+ DOM.appendChild(table, headerTableBody);
+ DOM.appendChild(headerTableBody, tr);
+ DOM.appendChild(hTableContainer, table);
+ DOM.appendChild(hTableWrapper, hTableContainer);
+ DOM.appendChild(div, hTableWrapper);
+ DOM.appendChild(div, columnSelector);
+ setElement(div);
+
+ setStyleName(CLASSNAME + "-header-wrap");
+
+ DOM.sinkEvents(columnSelector, Event.ONCLICK);
+
+ availableCells.put("0", new RowHeadersHeaderCell());
+ }
+
+ public void updateCellsFromUIDL(UIDL uidl) {
+ Iterator it = uidl.getChildIterator();
+ HashSet updated = new HashSet();
+ updated.add("0");
+ while (it.hasNext()) {
+ final UIDL col = (UIDL) it.next();
+ final String cid = col.getStringAttribute("cid");
+ updated.add(cid);
+ HeaderCell c = getHeaderCell(cid);
+ if (c == null) {
+ c = new HeaderCell(cid, col.getStringAttribute("caption"));
+ availableCells.put(cid, c);
+ if (initializedAndAttached) {
+ // we will need a column width recalculation
+ initializedAndAttached = false;
+ initialContentReceived = false;
+ isNewBody = true;
+ }
+ } else {
+ c.setText(col.getStringAttribute("caption"));
+ }
+
+ if (col.hasAttribute("sortable")) {
+ c.setSortable(true);
+ if (cid.equals(sortColumn)) {
+ c.setSorted(true);
+ } else {
+ c.setSorted(false);
+ }
+ }
+ if (col.hasAttribute("align")) {
+ c.setAlign(col.getStringAttribute("align").charAt(0));
+ }
+ if (col.hasAttribute("width")) {
+ final String width = col.getStringAttribute("width");
+ c.setWidth(Integer.parseInt(width));
+ }
+ // TODO icon
+ }
+ // check for orphaned header cells
+ it = availableCells.keySet().iterator();
+ while (it.hasNext()) {
+ String cid = (String) it.next();
+ if (!updated.contains(cid)) {
+ removeCell(cid);
+ it.remove();
+ }
+ }
+
+ }
+
+ public void enableColumn(String cid, int index) {
+ final HeaderCell c = getHeaderCell(cid);
+ if (!c.isEnabled() || getHeaderCell(index) != c) {
+ setHeaderCell(index, c);
+ if (c.getWidth() == -1) {
+ if (initializedAndAttached) {
+ // column is not drawn before,
+ // we will need a column width recalculation
+ initializedAndAttached = false;
+ initialContentReceived = false;
+ isNewBody = true;
+ }
+ }
+ }
+ }
+
+ public int getVisibleCellCount() {
+ return visibleCells.size();
+ }
+
+ public void setHorizontalScrollPosition(int scrollLeft) {
+ DOM.setElementPropertyInt(hTableWrapper, "scrollLeft", scrollLeft);
+ }
+
+ public void setColumnCollapsingAllowed(boolean cc) {
+ if (cc) {
+ DOM.setStyleAttribute(columnSelector, "display", "block");
+ } else {
+ DOM.setStyleAttribute(columnSelector, "display", "none");
+ }
+ }
+
+ public void disableBrowserIntelligence() {
+ DOM.setStyleAttribute(hTableContainer, "width", WRAPPER_WIDTH
+ + "px");
+ }
+
+ public void enableBrowserIntelligence() {
+ DOM.setStyleAttribute(hTableContainer, "width", "");
+ }
+
+ public void setHeaderCell(int index, HeaderCell cell) {
+ if (cell.isEnabled()) {
+ // we're moving the cell
+ DOM.removeChild(tr, cell.getElement());
+ orphan(cell);
+ }
+ if (index < visibleCells.size()) {
+ // insert to right slot
+ DOM.insertChild(tr, cell.getElement(), index);
+ adopt(cell);
+ visibleCells.insertElementAt(cell, index);
+
+ } else if (index == visibleCells.size()) {
+ // simply append
+ DOM.appendChild(tr, cell.getElement());
+ adopt(cell);
+ visibleCells.add(cell);
+ } else {
+ throw new RuntimeException(
+ "Header cells must be appended in order");
+ }
+ }
+
+ public HeaderCell getHeaderCell(int index) {
+ if (index < visibleCells.size()) {
+ return (HeaderCell) visibleCells.get(index);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Get's HeaderCell by it's column Key.
+ *
+ * Note that this returns HeaderCell even if it is currently collapsed.
+ *
+ * @param cid
+ * Column key of accessed HeaderCell
+ * @return HeaderCell
+ */
+ public HeaderCell getHeaderCell(String cid) {
+ return (HeaderCell) availableCells.get(cid);
+ }
+
+ public void moveCell(int oldIndex, int newIndex) {
+ final HeaderCell hCell = getHeaderCell(oldIndex);
+ final Element cell = hCell.getElement();
+
+ visibleCells.remove(oldIndex);
+ DOM.removeChild(tr, cell);
+
+ DOM.insertChild(tr, cell, newIndex);
+ visibleCells.insertElementAt(hCell, newIndex);
+ }
+
+ public Iterator iterator() {
+ return visibleCells.iterator();
+ }
+
+ public boolean remove(Widget w) {
+ if (visibleCells.contains(w)) {
+ visibleCells.remove(w);
+ orphan(w);
+ DOM.removeChild(DOM.getParent(w.getElement()), w.getElement());
+ return true;
+ }
+ return false;
+ }
+
+ public void removeCell(String colKey) {
+ final HeaderCell c = getHeaderCell(colKey);
+ remove(c);
+ }
+
+ private void focusSlot(int index) {
+ removeSlotFocus();
+ if (index > 0) {
+ DOM.setElementProperty(DOM.getFirstChild(DOM.getChild(tr,
+ index - 1)), "className", CLASSNAME + "-resizer "
+ + CLASSNAME + "-focus-slot-right");
+ } else {
+ DOM.setElementProperty(DOM.getFirstChild(DOM
+ .getChild(tr, index)), "className", CLASSNAME
+ + "-resizer " + CLASSNAME + "-focus-slot-left");
+ }
+ focusedSlot = index;
+ }
+
+ private void removeSlotFocus() {
+ if (focusedSlot < 0) {
+ return;
+ }
+ if (focusedSlot == 0) {
+ DOM.setElementProperty(DOM.getFirstChild(DOM.getChild(tr,
+ focusedSlot)), "className", CLASSNAME + "-resizer");
+ } else if (focusedSlot > 0) {
+ DOM.setElementProperty(DOM.getFirstChild(DOM.getChild(tr,
+ focusedSlot - 1)), "className", CLASSNAME + "-resizer");
+ }
+ focusedSlot = -1;
+ }
+
+ public void onBrowserEvent(Event event) {
+ if (enabled) {
+ if (DOM.compare(DOM.eventGetTarget(event), columnSelector)) {
+ final int left = DOM.getAbsoluteLeft(columnSelector);
+ final int top = DOM.getAbsoluteTop(columnSelector)
+ + DOM.getElementPropertyInt(columnSelector,
+ "offsetHeight");
+ client.getContextMenu().showAt(this, left, top);
+ }
+ }
+ }
+
+ class VisibleColumnAction extends Action {
+
+ String colKey;
+ private boolean collapsed;
+
+ public VisibleColumnAction(String colKey) {
+ super(IScrollTable.TableHead.this);
+ this.colKey = colKey;
+ caption = tHead.getHeaderCell(colKey).getCaption();
+ }
+
+ public void execute() {
+ client.getContextMenu().hide();
+ // toggle selected column
+ if (collapsedColumns.contains(colKey)) {
+ collapsedColumns.remove(colKey);
+ } else {
+ tHead.removeCell(colKey);
+ collapsedColumns.add(colKey);
+ }
+
+ // update variable to server
+ client.updateVariable(paintableId, "collapsedcolumns",
+ collapsedColumns.toArray(), false);
+ // let rowRequestHandler determine proper rows
+ rowRequestHandler.refreshContent();
+ }
+
+ public void setCollapsed(boolean b) {
+ collapsed = b;
+ }
+
+ /**
+ * Override default method to distinguish on/off columns
+ */
+ public String getHTML() {
+ final StringBuffer buf = new StringBuffer();
+ if (collapsed) {
+ buf.append("<span class=\"i-off\">");
+ } else {
+ buf.append("<span class=\"i-on\">");
+ }
+ buf.append(super.getHTML());
+ buf.append("</span>");
+
+ return buf.toString();
+ }
+
+ }
+
+ /*
+ * Returns columns as Action array for column select popup
+ */
+ public Action[] getActions() {
+ Object[] cols;
+ if (columnReordering) {
+ cols = columnOrder;
+ } else {
+ // if columnReordering is disabled, we need different way to get
+ // all available columns
+ cols = visibleColOrder;
+ cols = new Object[visibleColOrder.length
+ + collapsedColumns.size()];
+ int i;
+ for (i = 0; i < visibleColOrder.length; i++) {
+ cols[i] = visibleColOrder[i];
+ }
+ for (final Iterator it = collapsedColumns.iterator(); it
+ .hasNext();) {
+ cols[i++] = it.next();
+ }
+ }
+ final Action[] actions = new Action[cols.length];
+
+ for (int i = 0; i < cols.length; i++) {
+ final String cid = (String) cols[i];
+ final HeaderCell c = getHeaderCell(cid);
+ final VisibleColumnAction a = new VisibleColumnAction(c
+ .getColKey());
+ a.setCaption(c.getCaption());
+ if (!c.isEnabled()) {
+ a.setCollapsed(true);
+ }
+ actions[i] = a;
+ }
+ return actions;
+ }
+
+ public ApplicationConnection getClient() {
+ return client;
+ }
+
+ public String getPaintableId() {
+ return paintableId;
+ }
+
+ /**
+ * Returns column alignments for visible columns
+ */
+ public char[] getColumnAlignments() {
+ final Iterator it = visibleCells.iterator();
+ final char[] aligns = new char[visibleCells.size()];
+ int colIndex = 0;
+ while (it.hasNext()) {
+ aligns[colIndex++] = ((HeaderCell) it.next()).getAlign();
+ }
+ return aligns;
+ }
+
+ }
+
+ /**
+ * This Panel can only contain IScrollTableRow type of widgets. This
+ * "simulates" very large table, keeping spacers which take room of
+ * unrendered rows.
+ *
+ */
+ public class IScrollTableBody extends Panel {
+
+ public static final int CELL_EXTRA_WIDTH = 20;
+
+ public static final int DEFAULT_ROW_HEIGHT = 24;
+
+ /**
+ * Amount of padding inside one table cell (this is reduced from the
+ * "cellContent" element's width). You may override this in your own
+ * widgetset.
+ */
+ public static final int CELL_CONTENT_PADDING = 8;
+
+ private int rowHeight = -1;
+
+ private final List renderedRows = new Vector();
+
+ private boolean initDone = false;
+
+ Element preSpacer = DOM.createDiv();
+ Element postSpacer = DOM.createDiv();
+
+ Element container = DOM.createDiv();
+
+ Element tBody = DOM.createTBody();
+ Element table = DOM.createTable();
+
+ private int firstRendered;
+
+ private int lastRendered;
+
+ private char[] aligns;
+
+ IScrollTableBody() {
+ constructDOM();
+ setElement(container);
+ }
+
+ private void constructDOM() {
+ DOM.setElementProperty(table, "className", CLASSNAME + "-table");
+ DOM.setElementProperty(preSpacer, "className", CLASSNAME
+ + "-row-spacer");
+ DOM.setElementProperty(postSpacer, "className", CLASSNAME
+ + "-row-spacer");
+
+ DOM.appendChild(table, tBody);
+ DOM.appendChild(container, preSpacer);
+ DOM.appendChild(container, table);
+ DOM.appendChild(container, postSpacer);
+
+ }
+
+ public int getAvailableWidth() {
+ return DOM.getElementPropertyInt(preSpacer, "offsetWidth");
+ }
+
+ public void renderInitialRows(UIDL rowData, int firstIndex, int rows) {
+ firstRendered = firstIndex;
+ lastRendered = firstIndex + rows - 1;
+ final Iterator it = rowData.getChildIterator();
+ aligns = tHead.getColumnAlignments();
+ while (it.hasNext()) {
+ final IScrollTableRow row = new IScrollTableRow((UIDL) it
+ .next(), aligns);
+ addRow(row);
+ }
+ if (isAttached()) {
+ fixSpacers();
+ }
+ }
+
+ public void renderRows(UIDL rowData, int firstIndex, int rows) {
+ // FIXME REVIEW
+ aligns = tHead.getColumnAlignments();
+ final Iterator it = rowData.getChildIterator();
+ if (firstIndex == lastRendered + 1) {
+ while (it.hasNext()) {
+ final IScrollTableRow row = createRow((UIDL) it.next());
+ addRow(row);
+ lastRendered++;
+ }
+ fixSpacers();
+ } else if (firstIndex + rows == firstRendered) {
+ final IScrollTableRow[] rowArray = new IScrollTableRow[rows];
+ int i = rows;
+ while (it.hasNext()) {
+ i--;
+ rowArray[i] = createRow((UIDL) it.next());
+ }
+ for (i = 0; i < rows; i++) {
+ addRowBeforeFirstRendered(rowArray[i]);
+ firstRendered--;
+ }
+ } else {
+ // completely new set of rows
+ while (lastRendered + 1 > firstRendered) {
+ unlinkRow(false);
+ }
+ final IScrollTableRow row = createRow((UIDL) it.next());
+ firstRendered = firstIndex;
+ lastRendered = firstIndex - 1;
+ addRow(row);
+ lastRendered++;
+ setContainerHeight();
+ fixSpacers();
+ while (it.hasNext()) {
+ addRow(createRow((UIDL) it.next()));
+ lastRendered++;
+ }
+ fixSpacers();
+ DeferredCommand.addCommand(new Command() {
+ public void execute() {
+ // this may be a new set of rows due content change,
+ // ensure we have proper cache rows
+ int reactFirstRow = (int) (firstRowInViewPort - pageLength
+ * CACHE_REACT_RATE);
+ int reactLastRow = (int) (firstRowInViewPort
+ + pageLength + pageLength * CACHE_REACT_RATE);
+ if (reactFirstRow < 0) {
+ reactFirstRow = 0;
+ }
+ if (reactLastRow > totalRows) {
+ reactLastRow = totalRows - 1;
+ }
+ if (reactFirstRow < firstRendered
+ || reactLastRow > lastRendered) {
+ // re-fetch full cache area
+ reactFirstRow = (int) (firstRowInViewPort - pageLength
+ * CACHE_RATE);
+ reactLastRow = (int) (firstRowInViewPort
+ + pageLength + pageLength * CACHE_RATE);
+ if (reactFirstRow < 0) {
+ reactFirstRow = 0;
+ }
+ if (reactLastRow > totalRows) {
+ reactLastRow = totalRows - 1;
+ }
+ // fetch some lines before
+ rowRequestHandler.setReqFirstRow(reactFirstRow);
+ rowRequestHandler.setReqRows((int) (2 * pageLength
+ * CACHE_RATE + pageLength));
+ rowRequestHandler.deferRowFetch(1);
+ }
+ }
+ });
+ }
+ }
+
+ /**
+ * This method is used to instantiate new rows for this table. It
+ * automatically sets correct widths to rows cells and assigns correct
+ * client reference for child widgets.
+ *
+ * This method can be called only after table has been initialized
+ *
+ * @param uidl
+ */
+ private IScrollTableRow createRow(UIDL uidl) {
+ final IScrollTableRow row = new IScrollTableRow(uidl, aligns);
+ final int cells = DOM.getChildCount(row.getElement());
+ for (int i = 0; i < cells; i++) {
+ final Element cell = DOM.getChild(row.getElement(), i);
+ final int w = IScrollTable.this
+ .getColWidth(getColKeyByIndex(i));
+ DOM.setStyleAttribute(DOM.getFirstChild(cell), "width",
+ (w - CELL_CONTENT_PADDING) + "px");
+ DOM.setStyleAttribute(cell, "width", w + "px");
+ }
+ return row;
+ }
+
+ private void addRowBeforeFirstRendered(IScrollTableRow row) {
+ IScrollTableRow first = null;
+ if (renderedRows.size() > 0) {
+ first = (IScrollTableRow) renderedRows.get(0);
+ }
+ if (first != null && first.getStyleName().indexOf("-odd") == -1) {
+ row.setStyleName(CLASSNAME + "-row-odd");
+ }
+ if (row.isSelected()) {
+ row.addStyleName("i-selected");
+ }
+ DOM.insertChild(tBody, row.getElement(), 0);
+ adopt(row);
+ renderedRows.add(0, row);
+ }
+
+ private void addRow(IScrollTableRow row) {
+ IScrollTableRow last = null;
+ if (renderedRows.size() > 0) {
+ last = (IScrollTableRow) renderedRows
+ .get(renderedRows.size() - 1);
+ }
+ if (last != null && last.getStyleName().indexOf("-odd") == -1) {
+ row.setStyleName(CLASSNAME + "-row-odd");
+ }
+ if (row.isSelected()) {
+ row.addStyleName("i-selected");
+ }
+ DOM.appendChild(tBody, row.getElement());
+ adopt(row);
+ renderedRows.add(row);
+ }
+
+ public Iterator iterator() {
+ return renderedRows.iterator();
+ }
+
+ /**
+ * @return false if couldn't remove row
+ */
+ public boolean unlinkRow(boolean fromBeginning) {
+ if (lastRendered - firstRendered < 0) {
+ return false;
+ }
+ int index;
+ if (fromBeginning) {
+ index = 0;
+ firstRendered++;
+ } else {
+ index = renderedRows.size() - 1;
+ lastRendered--;
+ }
+ final IScrollTableRow toBeRemoved = (IScrollTableRow) renderedRows
+ .get(index);
+ client.unregisterChildPaintables(toBeRemoved);
+ DOM.removeChild(tBody, toBeRemoved.getElement());
+ orphan(toBeRemoved);
+ renderedRows.remove(index);
+ fixSpacers();
+ return true;
+ }
+
+ public boolean remove(Widget w) {
+ throw new UnsupportedOperationException();
+ }
+
+ protected void onAttach() {
+ super.onAttach();
+ setContainerHeight();
+ }
+
+ /**
+ * Fix container blocks height according to totalRows to avoid
+ * "bouncing" when scrolling
+ */
+ private void setContainerHeight() {
+ fixSpacers();
+ DOM.setStyleAttribute(container, "height", totalRows
+ * getRowHeight() + "px");
+ }
+
+ private void fixSpacers() {
+ int prepx = getRowHeight() * firstRendered;
+ if (prepx < 0) {
+ prepx = 0;
+ }
+ DOM.setStyleAttribute(preSpacer, "height", prepx + "px");
+ int postpx = getRowHeight() * (totalRows - 1 - lastRendered);
+ if (postpx < 0) {
+ postpx = 0;
+ }
+ DOM.setStyleAttribute(postSpacer, "height", postpx + "px");
+ }
+
+ public int getRowHeight() {
+ if (initDone) {
+ return rowHeight;
+ } else {
+ if (DOM.getChildCount(tBody) > 0) {
+ rowHeight = DOM
+ .getElementPropertyInt(tBody, "offsetHeight")
+ / DOM.getChildCount(tBody);
+ } else {
+ return DEFAULT_ROW_HEIGHT;
+ }
+ initDone = true;
+ return rowHeight;
+ }
+ }
+
+ public int getColWidth(int i) {
+ if (initDone) {
+ final Element e = DOM.getChild(DOM.getChild(tBody, 0), i);
+ return DOM.getElementPropertyInt(e, "offsetWidth");
+ } else {
+ return 0;
+ }
+ }
+
+ public void setColWidth(int colIndex, int w) {
+ final int rows = DOM.getChildCount(tBody);
+ for (int i = 0; i < rows; i++) {
+ final Element cell = DOM.getChild(DOM.getChild(tBody, i),
+ colIndex);
+ DOM.setStyleAttribute(DOM.getFirstChild(cell), "width",
+ (w - CELL_CONTENT_PADDING) + "px");
+ DOM.setStyleAttribute(cell, "width", w + "px");
+ }
+ }
+
+ public int getLastRendered() {
+ return lastRendered;
+ }
+
+ public int getFirstRendered() {
+ return firstRendered;
+ }
+
+ public void moveCol(int oldIndex, int newIndex) {
+
+ // loop all rows and move given index to its new place
+ final Iterator rows = iterator();
+ while (rows.hasNext()) {
+ final IScrollTableRow row = (IScrollTableRow) rows.next();
+
+ final Element td = DOM.getChild(row.getElement(), oldIndex);
+ DOM.removeChild(row.getElement(), td);
+
+ DOM.insertChild(row.getElement(), td, newIndex);
+
+ }
+
+ }
+
+ public class IScrollTableRow extends Panel implements ActionOwner {
+
+ Vector childWidgets = new Vector();
+ private boolean selected = false;
+ private final int rowKey;
+
+ private String[] actionKeys = null;
+
+ private IScrollTableRow(int rowKey) {
+ this.rowKey = rowKey;
+ setElement(DOM.createElement("tr"));
+ DOM.sinkEvents(getElement(), Event.ONCLICK);
+ attachContextMenuEvent(getElement());
+ setStyleName(CLASSNAME + "-row");
+ }
+
+ protected void onDetach() {
+ Util.removeContextMenuEvent(getElement());
+ super.onDetach();
+ }
+
+ /**
+ * Attaches context menu event handler to given element. Attached
+ * handler fires showContextMenu function.
+ *
+ * @param el
+ * element where to attach contenxt menu event
+ */
+ private native void attachContextMenuEvent(Element el)
+ /*-{
+ var row = this;
+ el.oncontextmenu = function(e) {
+ if(!e)
+ e = $wnd.event;
+ row.@com.itmill.toolkit.terminal.gwt.client.ui.IScrollTable.IScrollTableBody.IScrollTableRow::showContextMenu(Lcom/google/gwt/user/client/Event;)(e);
+ return false;
+ };
+ }-*/;
+
+ public String getKey() {
+ return String.valueOf(rowKey);
+ }
+
+ public IScrollTableRow(UIDL uidl, char[] aligns) {
+ this(uidl.getIntAttribute("key"));
+
+ tHead.getColumnAlignments();
+ int col = 0;
+ // row header
+ if (showRowHeaders) {
+ addCell(uidl.getStringAttribute("caption"), aligns[col++],
+ "");
+ }
+
+ if (uidl.hasAttribute("al")) {
+ actionKeys = uidl.getStringArrayAttribute("al");
+ }
+
+ final Iterator cells = uidl.getChildIterator();
+ while (cells.hasNext()) {
+ final Object cell = cells.next();
+ if (cell instanceof String) {
+ String style = "";
+ if (uidl.hasAttribute("style-" + col)) {
+ style = uidl.getStringAttribute("style-" + col);
+ }
+ addCell(cell.toString(), aligns[col++], style);
+ } else {
+ final Paintable cellContent = client
+ .getPaintable((UIDL) cell);
+ (cellContent).updateFromUIDL((UIDL) cell, client);
+ String style = "";
+ if (uidl.hasAttribute("style")) {
+ style = uidl.getStringAttribute("style");
+ }
+ addCell((Widget) cellContent, aligns[col++], style);
+ }
+ }
+ if (uidl.hasAttribute("selected") && !isSelected()) {
+ toggleSelection();
+ }
+ }
+
+ public void addCell(String text, char align, String style) {
+ // String only content is optimized by not using Label widget
+ final Element td = DOM.createTD();
+ final Element container = DOM.createDiv();
+ String className = CLASSNAME + "-cell-content";
+ if (style != null && !style.equals("")) {
+ className += " " + CLASSNAME + "-cell-content-" + style;
+ }
+
+ DOM.setElementProperty(container, "className", className);
+ DOM.setInnerHTML(container, text);
+ if (align != ALIGN_LEFT) {
+ switch (align) {
+ case ALIGN_CENTER:
+ DOM.setStyleAttribute(container, "textAlign", "center");
+ break;
+ case ALIGN_RIGHT:
+ default:
+ DOM.setStyleAttribute(container, "textAlign", "right");
+ break;
+ }
+ }
+ DOM.appendChild(td, container);
+ DOM.appendChild(getElement(), td);
+ }
+
+ public void addCell(Widget w, char align, String style) {
+ final Element td = DOM.createTD();
+ final Element container = DOM.createDiv();
+ String className = CLASSNAME + "-cell-content";
+ if (style != null && !style.equals("")) {
+ className += " " + CLASSNAME + "-cell-content-" + style;
+ }
+ DOM.setElementProperty(container, "className", className);
+ // TODO most components work with this, but not all (e.g.
+ // Select)
+ // Old comment: make widget cells respect align.
+ // text-align:center for IE, margin: auto for others
+ if (align != ALIGN_LEFT) {
+ switch (align) {
+ case ALIGN_CENTER:
+ DOM.setStyleAttribute(container, "textAlign", "center");
+ break;
+ case ALIGN_RIGHT:
+ default:
+ DOM.setStyleAttribute(container, "textAlign", "right");
+ break;
+ }
+ }
+ DOM.appendChild(td, container);
+ DOM.appendChild(getElement(), td);
+ DOM.appendChild(container, w.getElement());
+ adopt(w);
+ childWidgets.add(w);
+ }
+
+ public Iterator iterator() {
+ return childWidgets.iterator();
+ }
+
+ public boolean remove(Widget w) {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ /*
+ * React on click that occur on content cells only
+ */
+ public void onBrowserEvent(Event event) {
+ switch (DOM.eventGetType(event)) {
+ case Event.ONCLICK:
+ final Element tdOrTr = DOM.getParent(DOM
+ .eventGetTarget(event));
+ if (DOM.compare(getElement(), tdOrTr)
+ || DOM.compare(getElement(), DOM.getParent(tdOrTr))) {
+ if (selectMode > Table.SELECT_MODE_NONE) {
+ toggleSelection();
+ client.updateVariable(paintableId, "selected",
+ selectedRowKeys.toArray(), immediate);
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+ super.onBrowserEvent(event);
+ }
+
+ public void showContextMenu(Event event) {
+ ApplicationConnection.getConsole().log("Context menu");
+ if (enabled && actionKeys != null) {
+ int left = DOM.eventGetClientX(event);
+ int top = DOM.eventGetClientY(event);
+ top += Window.getScrollTop();
+ left += Window.getScrollLeft();
+ client.getContextMenu().showAt(this, left, top);
+ }
+ }
+
+ public boolean isSelected() {
+ return selected;
+ }
+
+ private void toggleSelection() {
+ selected = !selected;
+ if (selected) {
+ if (selectMode == Table.SELECT_MODE_SINGLE) {
+ deselectAll();
+ }
+ selectedRowKeys.add(String.valueOf(rowKey));
+ addStyleName("i-selected");
+ } else {
+ selectedRowKeys.remove(String.valueOf(rowKey));
+ removeStyleName("i-selected");
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.itmill.toolkit.terminal.gwt.client.ui.IActionOwner#getActions()
+ */
+ public Action[] getActions() {
+ if (actionKeys == null) {
+ return new Action[] {};
+ }
+ final Action[] actions = new Action[actionKeys.length];
+ for (int i = 0; i < actions.length; i++) {
+ final String actionKey = actionKeys[i];
+ final TreeAction a = new TreeAction(this, String
+ .valueOf(rowKey), actionKey);
+ a.setCaption(getActionCaption(actionKey));
+ a.setIconUrl(getActionIcon(actionKey));
+ actions[i] = a;
+ }
+ return actions;
+ }
+
+ public ApplicationConnection getClient() {
+ return client;
+ }
+
+ public String getPaintableId() {
+ return paintableId;
+ }
+ }
+ }
+
+ public void deselectAll() {
+ final Object[] keys = selectedRowKeys.toArray();
+ for (int i = 0; i < keys.length; i++) {
+ final IScrollTableRow row = getRenderedRowByKey((String) keys[i]);
+ if (row != null && row.isSelected()) {
+ row.toggleSelection();
+ }
+ }
+ // still ensure all selects are removed from (not necessary rendered)
+ selectedRowKeys.clear();
+
+ }
+
+ public void add(Widget w) {
+ throw new UnsupportedOperationException(
+ "ITable can contain only rows created by itself.");
+ }
+
+ public void clear() {
+ panel.clear();
+ }
+
+ public Iterator iterator() {
+ return panel.iterator();
+ }
+
+ public boolean remove(Widget w) {
+ return panel.remove(w);
+ }
+
+ public void mySetHeight(String height) {
+ // workaround very common 100% height problem - extract borders
+ if (height.equals("100%")) {
+ final int borders = getBorderSpace();
+ final Element parentElem = DOM.getParent(getElement());
+
+ // put table away from flow for a moment
+ DOM.setStyleAttribute(getElement(), "position", "absolute");
+ // get containers natural space for table
+ int availPixels = DOM.getElementPropertyInt(parentElem,
+ "offsetHeight");
+ if (Util.isIE()) {
+ if (availPixels == 0) {
+ // In complex layouts IE sometimes rather randomly returns 0
+ // although container really has height. Use old value if
+ // one exits.
+ if (oldAvailPixels > 0) {
+ availPixels = oldAvailPixels;
+ }
+ } else {
+ oldAvailPixels = availPixels;
+ }
+ }
+ // put table back to flow
+ DOM.setStyleAttribute(getElement(), "position", "static");
+ // set 100% height with borders
+ int pixelSize = (availPixels - borders);
+ if (pixelSize < 0) {
+ pixelSize = 0;
+ }
+ super.setHeight(pixelSize + "px");
+ } else {
+ // normally height don't include borders
+ super.setHeight(height);
+ }
+ }
+
+ private int getBorderSpace() {
+ final Element el = getElement();
+ return DOM.getElementPropertyInt(el, "offsetHeight")
+ - DOM.getElementPropertyInt(el, "clientHeight");
+ }
+
+ public void setWidth(String width) {
+ // NOP size handled internally
+ }
+
+ public void setHeight(String height) {
+ // NOP size handled internally
+ }
+
+ /*
+ * Overridden due Table might not survive of visibility change (scroll pos
+ * lost). Example ITabPanel just set contained components invisible and back
+ * when changing tabs.
+ */
+ public void setVisible(boolean visible) {
+ if (isVisible() != visible) {
+ super.setVisible(visible);
+ if (initializedAndAttached) {
+ if (visible) {
+ DeferredCommand.addCommand(new Command() {
+ public void execute() {
+ bodyContainer.setScrollPosition(firstRowInViewPort
+ * tBody.getRowHeight());
+ }
+ });
+ }
+ }
+ }
+ }
+
+}
--- /dev/null
+/* \r
+@ITMillApache2LicenseForJavaFiles@\r
+ */\r
+// \r
+package com.itmill.toolkit.terminal.gwt.client.ui;\r
+\r
+import com.google.gwt.user.client.Command;\r
+import com.google.gwt.user.client.DOM;\r
+import com.google.gwt.user.client.DeferredCommand;\r
+import com.google.gwt.user.client.Element;\r
+import com.google.gwt.user.client.Event;\r
+import com.google.gwt.user.client.ui.Widget;\r
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;\r
+import com.itmill.toolkit.terminal.gwt.client.ContainerResizedListener;\r
+import com.itmill.toolkit.terminal.gwt.client.Paintable;\r
+import com.itmill.toolkit.terminal.gwt.client.UIDL;\r
+import com.itmill.toolkit.terminal.gwt.client.Util;\r
+\r
+public class ISlider extends Widget implements Paintable, Field,\r
+ ContainerResizedListener {\r
+\r
+ public static final String CLASSNAME = "i-slider";\r
+\r
+ /**\r
+ * Minimum size (width or height, depending on orientation) of the slider\r
+ * base.\r
+ */\r
+ private static final int MIN_SIZE = 50;\r
+\r
+ ApplicationConnection client;\r
+\r
+ String id;\r
+\r
+ private boolean immediate;\r
+ private boolean disabled;\r
+ private boolean readonly;\r
+ private boolean scrollbarStyle;\r
+\r
+ private int handleSize;\r
+ private double min;\r
+ private double max;\r
+ private int resolution;\r
+ private Double value;\r
+ private boolean vertical;\r
+ private int size = -1;\r
+ private boolean arrows;\r
+\r
+ /* DOM element for slider's base */\r
+ private final Element base;\r
+\r
+ /* DOM element for slider's handle */\r
+ private final Element handle;\r
+\r
+ /* DOM element for decrement arrow */\r
+ private final Element smaller;\r
+\r
+ /* DOM element for increment arrow */\r
+ private final Element bigger;\r
+\r
+ /* Temporary dragging/animation variables */\r
+ private boolean dragging = false;\r
+\r
+ public ISlider() {\r
+ super();\r
+\r
+ setElement(DOM.createDiv());\r
+ base = DOM.createDiv();\r
+ handle = DOM.createDiv();\r
+ smaller = DOM.createDiv();\r
+ bigger = DOM.createDiv();\r
+\r
+ setStyleName(CLASSNAME);\r
+ DOM.setElementProperty(base, "className", CLASSNAME + "-base");\r
+ DOM.setElementProperty(handle, "className", CLASSNAME + "-handle");\r
+ DOM.setElementProperty(smaller, "className", CLASSNAME + "-smaller");\r
+ DOM.setElementProperty(bigger, "className", CLASSNAME + "-bigger");\r
+\r
+ DOM.appendChild(getElement(), bigger);\r
+ DOM.appendChild(getElement(), smaller);\r
+ DOM.appendChild(getElement(), base);\r
+ DOM.appendChild(base, handle);\r
+\r
+ // Hide initially\r
+ DOM.setStyleAttribute(smaller, "display", "none");\r
+ DOM.setStyleAttribute(bigger, "display", "none");\r
+ DOM.setStyleAttribute(handle, "visibility", "hidden");\r
+\r
+ DOM.sinkEvents(getElement(), Event.MOUSEEVENTS | Event.ONMOUSEWHEEL);\r
+ DOM.sinkEvents(base, Event.ONCLICK);\r
+ DOM.sinkEvents(handle, Event.MOUSEEVENTS);\r
+ DOM.sinkEvents(smaller, Event.ONMOUSEDOWN | Event.ONMOUSEUP\r
+ | Event.ONMOUSEOUT);\r
+ DOM.sinkEvents(bigger, Event.ONMOUSEDOWN | Event.ONMOUSEUP\r
+ | Event.ONMOUSEOUT);\r
+ }\r
+\r
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {\r
+\r
+ this.client = client;\r
+ id = uidl.getId();\r
+\r
+ // Ensure correct implementation\r
+ if (client.updateComponent(this, uidl, true)) {\r
+ return;\r
+ }\r
+\r
+ immediate = uidl.getBooleanAttribute("immediate");\r
+ disabled = uidl.getBooleanAttribute("disabled");\r
+ readonly = uidl.getBooleanAttribute("readonly");\r
+\r
+ vertical = uidl.hasAttribute("vertical");\r
+ arrows = uidl.hasAttribute("arrows");\r
+\r
+ String style = "";\r
+ if (uidl.hasAttribute("style")) {\r
+ style = uidl.getStringAttribute("style");\r
+ }\r
+\r
+ scrollbarStyle = style.indexOf("scrollbar") > -1;\r
+\r
+ if (arrows) {\r
+ DOM.setStyleAttribute(smaller, "display", "block");\r
+ DOM.setStyleAttribute(bigger, "display", "block");\r
+ }\r
+\r
+ if (vertical) {\r
+ addStyleName(CLASSNAME + "-vertical");\r
+ } else {\r
+ removeStyleName(CLASSNAME + "-vertical");\r
+ }\r
+\r
+ min = uidl.getDoubleAttribute("min");\r
+ max = uidl.getDoubleAttribute("max");\r
+ resolution = uidl.getIntAttribute("resolution");\r
+ value = new Double(uidl.getDoubleVariable("value"));\r
+\r
+ handleSize = uidl.getIntAttribute("hsize");\r
+\r
+ buildBase();\r
+\r
+ if (!vertical) {\r
+ // Draw handle with a delay to allow base to gain maximum width\r
+ DeferredCommand.addCommand(new Command() {\r
+ public void execute() {\r
+ buildHandle();\r
+ setValue(value, false, false);\r
+ }\r
+ });\r
+ } else {\r
+ buildHandle();\r
+ setValue(value, false, false);\r
+ }\r
+ }\r
+\r
+ private void buildBase() {\r
+ final String styleAttribute = vertical ? "height" : "width";\r
+ final String domProperty = vertical ? "offsetHeight" : "offsetWidth";\r
+\r
+ if (size == -1) {\r
+ final Element p = DOM.getParent(getElement());\r
+ if (DOM.getElementPropertyInt(p, domProperty) > 50) {\r
+ if (vertical) {\r
+ setHeight();\r
+ } else {\r
+ DOM.setStyleAttribute(base, styleAttribute, "");\r
+ }\r
+ } else {\r
+ // Set minimum size and adjust after all components have\r
+ // (supposedly) been drawn completely.\r
+ DOM.setStyleAttribute(base, styleAttribute, MIN_SIZE + "px");\r
+ DeferredCommand.addCommand(new Command() {\r
+ public void execute() {\r
+ final Element p = DOM.getParent(getElement());\r
+ if (DOM.getElementPropertyInt(p, domProperty) > (MIN_SIZE + 5)) {\r
+ if (vertical) {\r
+ setHeight();\r
+ } else {\r
+ DOM.setStyleAttribute(base, styleAttribute, "");\r
+ }\r
+ // Ensure correct position\r
+ setValue(value, false, false);\r
+ }\r
+ }\r
+ });\r
+ }\r
+ } else {\r
+ DOM.setStyleAttribute(base, styleAttribute, size + "px");\r
+ }\r
+\r
+ // TODO attach listeners for focusing and arrow keys\r
+ }\r
+\r
+ private void buildHandle() {\r
+ final String styleAttribute = vertical ? "height" : "width";\r
+ final String handleAttribute = vertical ? "marginTop" : "marginLeft";\r
+ final String domProperty = vertical ? "offsetHeight" : "offsetWidth";\r
+\r
+ DOM.setStyleAttribute(handle, handleAttribute, "0");\r
+\r
+ if (scrollbarStyle) {\r
+ // Only stretch the handle if scrollbar style is set.\r
+ int s = (int) (Double.parseDouble(DOM.getElementProperty(base,\r
+ domProperty)) / 100 * handleSize);\r
+ if (handleSize == -1) {\r
+ final int baseS = Integer.parseInt(DOM.getElementProperty(base,\r
+ domProperty));\r
+ final double range = (max - min) * (resolution + 1) * 3;\r
+ s = (int) (baseS - range);\r
+ }\r
+ if (s < 3) {\r
+ s = 3;\r
+ }\r
+ DOM.setStyleAttribute(handle, styleAttribute, s + "px");\r
+ } else {\r
+ DOM.setStyleAttribute(handle, styleAttribute, "");\r
+ }\r
+\r
+ // Restore visibility\r
+ DOM.setStyleAttribute(handle, "visibility", "visible");\r
+\r
+ }\r
+\r
+ private void setValue(Double value, boolean animate, boolean updateToServer) {\r
+ if (value == null) {\r
+ return;\r
+ }\r
+\r
+ if (value.doubleValue() < min) {\r
+ value = new Double(min);\r
+ } else if (value.doubleValue() > max) {\r
+ value = new Double(max);\r
+ }\r
+\r
+ // Update handle position\r
+ final String styleAttribute = vertical ? "marginTop" : "marginLeft";\r
+ final String domProperty = vertical ? "offsetHeight" : "offsetWidth";\r
+ final int handleSize = Integer.parseInt(DOM.getElementProperty(handle,\r
+ domProperty));\r
+ final int baseSize = Integer.parseInt(DOM.getElementProperty(base,\r
+ domProperty));\r
+ final int range = baseSize - handleSize;\r
+ double v = value.doubleValue();\r
+ // Round value to resolution\r
+ if (resolution > 0) {\r
+ v = Math.round(v * Math.pow(10, resolution));\r
+ v = v / Math.pow(10, resolution);\r
+ } else {\r
+ v = Math.round(v);\r
+ }\r
+ final double valueRange = max - min;\r
+ double p = 0;\r
+ if (valueRange > 0) {\r
+ p = range * ((v - min) / valueRange);\r
+ }\r
+ if (p < 0) {\r
+ p = 0;\r
+ }\r
+ if (vertical) {\r
+ // IE6 rounding behaves a little unstable, reduce one pixel so the\r
+ // containing element (base) won't expand without limits\r
+ p = range - p - (Util.isIE6() ? 1 : 0);\r
+ }\r
+ final double pos = p;\r
+\r
+ final int current = DOM.getIntStyleAttribute(handle, styleAttribute);\r
+\r
+ DOM.setStyleAttribute(handle, styleAttribute, (Math.round(pos)) + "px");\r
+\r
+ // TODO give more detailed info when dragging and do roundup\r
+ DOM.setElementAttribute(handle, "title", "" + v);\r
+\r
+ // Update value\r
+ this.value = new Double(v);\r
+\r
+ if (updateToServer) {\r
+ client.updateVariable(id, "value", this.value.doubleValue(),\r
+ immediate);\r
+ }\r
+ }\r
+\r
+ public void onBrowserEvent(Event event) {\r
+ if (disabled || readonly) {\r
+ return;\r
+ }\r
+ final Element targ = DOM.eventGetTarget(event);\r
+\r
+ if (DOM.eventGetType(event) == Event.ONMOUSEWHEEL) {\r
+ processMouseWheelEvent(event);\r
+ } else if (dragging || DOM.compare(targ, handle)) {\r
+ processHandleEvent(event);\r
+ } else if (DOM.compare(targ, smaller)) {\r
+ decreaseValue(event);\r
+ } else if (DOM.compare(targ, bigger)) {\r
+ increaseValue(event);\r
+ } else {\r
+ processBaseEvent(event);\r
+ }\r
+ }\r
+\r
+ private void processMouseWheelEvent(Event event) {\r
+ final int dir = DOM.eventGetMouseWheelVelocityY(event);\r
+ if (dir < 0) {\r
+ increaseValue(event);\r
+ } else {\r
+ decreaseValue(event);\r
+ }\r
+ DOM.eventPreventDefault(event);\r
+ DOM.eventCancelBubble(event, true);\r
+ }\r
+\r
+ private void processHandleEvent(Event event) {\r
+ switch (DOM.eventGetType(event)) {\r
+ case Event.ONMOUSEDOWN:\r
+ if (!disabled && !readonly) {\r
+ dragging = true;\r
+ DOM.setCapture(getElement());\r
+ DOM.eventPreventDefault(event); // prevent selecting text\r
+ DOM.eventCancelBubble(event, true);\r
+ }\r
+ break;\r
+ case Event.ONMOUSEMOVE:\r
+ if (dragging) {\r
+ // DOM.setCapture(getElement());\r
+ setValueByEvent(event, false, false);\r
+ }\r
+ break;\r
+ case Event.ONMOUSEUP:\r
+ dragging = false;\r
+ DOM.releaseCapture(getElement());\r
+ setValueByEvent(event, true, true);\r
+ break;\r
+ default:\r
+ break;\r
+ }\r
+ }\r
+\r
+ private void processBaseEvent(Event event) {\r
+ if (DOM.eventGetType(event) == Event.ONMOUSEDOWN) {\r
+ if (!disabled && !readonly && !dragging) {\r
+ setValueByEvent(event, true, true);\r
+ DOM.eventCancelBubble(event, true);\r
+ }\r
+ } else if (DOM.eventGetType(event) == Event.ONMOUSEDOWN && dragging) {\r
+ dragging = false;\r
+ DOM.releaseCapture(getElement());\r
+ setValueByEvent(event, true, true);\r
+ }\r
+ }\r
+\r
+ private void decreaseValue(Event event) {\r
+ setValue(new Double(value.doubleValue() - Math.pow(10, -resolution)),\r
+ false, true);\r
+ }\r
+\r
+ private void increaseValue(Event event) {\r
+ setValue(new Double(value.doubleValue() + Math.pow(10, -resolution)),\r
+ false, true);\r
+ }\r
+\r
+ private void setValueByEvent(Event event, boolean animate, boolean roundup) {\r
+ double v = min; // Fallback to min\r
+\r
+ final int coord = vertical ? DOM.eventGetClientY(event) : DOM\r
+ .eventGetClientX(event);\r
+ final String domProperty = vertical ? "offsetHeight" : "offsetWidth";\r
+\r
+ final double handleSize = Integer.parseInt(DOM.getElementProperty(\r
+ handle, domProperty));\r
+ final double baseSize = Integer.parseInt(DOM.getElementProperty(base,\r
+ domProperty));\r
+ final double baseOffset = vertical ? DOM.getAbsoluteTop(base)\r
+ - handleSize / 2 : DOM.getAbsoluteLeft(base) + handleSize / 2;\r
+\r
+ if (vertical) {\r
+ v = ((baseSize - (coord - baseOffset)) / (baseSize - handleSize))\r
+ * (max - min) + min;\r
+ } else {\r
+ v = ((coord - baseOffset) / (baseSize - handleSize)) * (max - min)\r
+ + min;\r
+ }\r
+\r
+ if (v < min) {\r
+ v = min;\r
+ } else if (v > max) {\r
+ v = max;\r
+ }\r
+\r
+ setValue(new Double(v), animate, roundup);\r
+ }\r
+\r
+ public void iLayout() {\r
+ if (vertical) {\r
+ setHeight();\r
+ }\r
+ // Update handle position\r
+ setValue(value, false, false);\r
+ }\r
+\r
+ private void setHeight() {\r
+ if (size == -1) {\r
+ // Calculate decoration size\r
+ DOM.setStyleAttribute(base, "height", "0");\r
+ DOM.setStyleAttribute(base, "overflow", "hidden");\r
+ int h = DOM.getElementPropertyInt(getElement(), "offsetHeight");\r
+ if (h < MIN_SIZE) {\r
+ h = MIN_SIZE;\r
+ }\r
+ DOM.setStyleAttribute(base, "height", h + "px");\r
+ } else {\r
+ DOM.setStyleAttribute(base, "height", size + "px");\r
+ }\r
+ DOM.setStyleAttribute(base, "overflow", "");\r
+ }\r
+\r
+}\r
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client.ui;
+
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.DeferredCommand;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.ComplexPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;
+import com.itmill.toolkit.terminal.gwt.client.ContainerResizedListener;
+import com.itmill.toolkit.terminal.gwt.client.Paintable;
+import com.itmill.toolkit.terminal.gwt.client.UIDL;
+import com.itmill.toolkit.terminal.gwt.client.Util;
+
+public class ISplitPanel extends ComplexPanel implements Paintable,
+ ContainerResizedListener {
+ public static final String CLASSNAME = "i-splitpanel";
+
+ public static final int ORIENTATION_HORIZONTAL = 0;
+
+ public static final int ORIENTATION_VERTICAL = 1;
+
+ private static final int MIN_SIZE = 30;
+
+ private int orientation = ORIENTATION_HORIZONTAL;
+
+ private Widget firstChild;
+
+ private Widget secondChild;
+
+ private final Element wrapper = DOM.createDiv();
+
+ private final Element firstContainer = DOM.createDiv();
+
+ private final Element secondContainer = DOM.createDiv();
+
+ private final Element splitter = DOM.createDiv();
+
+ private boolean resizing;
+
+ private int origX;
+
+ private int origY;
+
+ private int origMouseX;
+
+ private int origMouseY;
+
+ private boolean locked;
+
+ private String splitterStyleName;
+
+ public ISplitPanel() {
+ this(ORIENTATION_HORIZONTAL);
+ }
+
+ public ISplitPanel(int orientation) {
+ setElement(DOM.createDiv());
+ switch (orientation) {
+ case ORIENTATION_HORIZONTAL:
+ setStyleName(CLASSNAME + "-horizontal");
+ break;
+ case ORIENTATION_VERTICAL:
+ default:
+ setStyleName(CLASSNAME + "-vertical");
+ break;
+ }
+ // size below will be overridden in update from uidl, initial size
+ // needed to keep IE alive
+ setWidth(MIN_SIZE + "px");
+ setHeight(MIN_SIZE + "px");
+ constructDom();
+ setOrientation(orientation);
+ DOM.sinkEvents(splitter, (Event.MOUSEEVENTS));
+ DOM.sinkEvents(getElement(), (Event.MOUSEEVENTS));
+ }
+
+ protected void constructDom() {
+ DOM.appendChild(splitter, DOM.createDiv()); // for styling
+ DOM.appendChild(getElement(), wrapper);
+ DOM.setStyleAttribute(wrapper, "position", "relative");
+ DOM.setStyleAttribute(wrapper, "width", "100%");
+ DOM.setStyleAttribute(wrapper, "height", "100%");
+
+ DOM.appendChild(wrapper, splitter);
+ DOM.appendChild(wrapper, secondContainer);
+ DOM.appendChild(wrapper, firstContainer);
+
+ DOM.setStyleAttribute(splitter, "position", "absolute");
+ DOM.setStyleAttribute(secondContainer, "position", "absolute");
+
+ DOM.setStyleAttribute(firstContainer, "overflow", "auto");
+ DOM.setStyleAttribute(secondContainer, "overflow", "auto");
+ if (Util.isIE7()) {
+ /*
+ * Part I of IE7 weirdness hack, will be set to auto in layout phase
+ *
+ * With IE7 one will sometimes get scrollbars with overflow auto
+ * even though there is nothing to scroll (content fits into area).
+ *
+ */
+ DOM.setStyleAttribute(firstContainer, "overflow", "hidden");
+ DOM.setStyleAttribute(secondContainer, "overflow", "hidden");
+ }
+ }
+
+ private void setOrientation(int orientation) {
+ this.orientation = orientation;
+ if (orientation == ORIENTATION_HORIZONTAL) {
+ DOM.setStyleAttribute(splitter, "height", "100%");
+ DOM.setStyleAttribute(firstContainer, "height", "100%");
+ DOM.setStyleAttribute(secondContainer, "height", "100%");
+ } else {
+ DOM.setStyleAttribute(splitter, "width", "100%");
+ DOM.setStyleAttribute(firstContainer, "width", "100%");
+ DOM.setStyleAttribute(secondContainer, "width", "100%");
+ }
+
+ splitterStyleName = CLASSNAME
+ + (orientation == ORIENTATION_HORIZONTAL ? "-hsplitter"
+ : "-vsplitter");
+ DOM.setElementProperty(splitter, "className", splitterStyleName);
+ }
+
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+ if (client.updateComponent(this, uidl, true)) {
+ return;
+ }
+
+ setSplitPosition(uidl.getStringAttribute("position"));
+
+ locked = uidl.hasAttribute("locked");
+ if (locked) {
+ DOM.setElementProperty(splitter, "className", splitterStyleName
+ + "-locked");
+ } else {
+ DOM.setElementProperty(splitter, "className", splitterStyleName);
+ }
+
+ final Paintable newFirstChild = client.getPaintable(uidl
+ .getChildUIDL(0));
+ final Paintable newSecondChild = client.getPaintable(uidl
+ .getChildUIDL(1));
+ if (firstChild != newFirstChild) {
+ if (firstChild != null) {
+ client.unregisterPaintable((Paintable) firstChild);
+ }
+ setFirstWidget((Widget) newFirstChild);
+ }
+ if (secondChild != newSecondChild) {
+ if (secondChild != null) {
+ client.unregisterPaintable((Paintable) secondChild);
+ }
+ setSecondWidget((Widget) newSecondChild);
+ }
+ newFirstChild.updateFromUIDL(uidl.getChildUIDL(0), client);
+ newSecondChild.updateFromUIDL(uidl.getChildUIDL(1), client);
+
+ if (Util.isIE7()) {
+ // Part III of IE7 hack
+ DeferredCommand.addCommand(new Command() {
+ public void execute() {
+ iLayout();
+ }
+ });
+ }
+ }
+
+ private void setSplitPosition(String pos) {
+ if (orientation == ORIENTATION_HORIZONTAL) {
+ DOM.setStyleAttribute(splitter, "left", pos);
+ } else {
+ DOM.setStyleAttribute(splitter, "top", pos);
+ }
+ iLayout();
+ }
+
+ /*
+ * Calculates absolutely positioned container places/sizes (non-Javadoc)
+ *
+ * @see com.itmill.toolkit.terminal.gwt.client.NeedsLayout#layout()
+ */
+ public void iLayout() {
+ if (!isAttached()) {
+ return;
+ }
+ int wholeSize;
+ int pixelPosition;
+
+ DOM.setStyleAttribute(firstContainer, "overflow", "hidden");
+ DOM.setStyleAttribute(secondContainer, "overflow", "hidden");
+
+ switch (orientation) {
+ case ORIENTATION_HORIZONTAL:
+ wholeSize = DOM.getElementPropertyInt(wrapper, "clientWidth");
+ pixelPosition = DOM.getElementPropertyInt(splitter, "offsetLeft");
+
+ // reposition splitter in case it is out of box
+ if (pixelPosition > 0
+ && pixelPosition + getSplitterSize() > wholeSize) {
+ pixelPosition = wholeSize - getSplitterSize();
+ if (pixelPosition < 0) {
+ pixelPosition = 0;
+ }
+ setSplitPosition(pixelPosition + "px");
+ return;
+ }
+
+ DOM
+ .setStyleAttribute(firstContainer, "width", pixelPosition
+ + "px");
+ int secondContainerWidth = (wholeSize - pixelPosition - getSplitterSize());
+ if (secondContainerWidth < 0) {
+ secondContainerWidth = 0;
+ }
+ DOM.setStyleAttribute(secondContainer, "width",
+ secondContainerWidth + "px");
+ DOM.setStyleAttribute(secondContainer, "left",
+ (pixelPosition + getSplitterSize()) + "px");
+
+ break;
+ case ORIENTATION_VERTICAL:
+ wholeSize = DOM.getElementPropertyInt(wrapper, "clientHeight");
+ pixelPosition = DOM.getElementPropertyInt(splitter, "offsetTop");
+
+ // reposition splitter in case it is out of box
+ if (pixelPosition > 0
+ && pixelPosition + getSplitterSize() > wholeSize) {
+ pixelPosition = wholeSize - getSplitterSize();
+ if (pixelPosition < 0) {
+ pixelPosition = 0;
+ }
+ setSplitPosition(pixelPosition + "px");
+ return;
+ }
+
+ DOM.setStyleAttribute(firstContainer, "height", pixelPosition
+ + "px");
+ int secondContainerHeight = (wholeSize - pixelPosition - getSplitterSize());
+ if (secondContainerHeight < 0) {
+ secondContainerHeight = 0;
+ }
+ DOM.setStyleAttribute(secondContainer, "height",
+ secondContainerHeight + "px");
+ DOM.setStyleAttribute(secondContainer, "top",
+ (pixelPosition + getSplitterSize()) + "px");
+ break;
+ }
+
+ if (Util.isIE7()) {
+ // Part I of IE7 weirdness hack, will be set to auto in layout phase
+ Util.runDescendentsLayout(this);
+ DeferredCommand.addCommand(new Command() {
+ public void execute() {
+ DOM.setStyleAttribute(firstContainer, "overflow", "auto");
+ DOM.setStyleAttribute(secondContainer, "overflow", "auto");
+ }
+ });
+ } else {
+ Util.runDescendentsLayout(this);
+ DOM.setStyleAttribute(firstContainer, "overflow", "auto");
+ DOM.setStyleAttribute(secondContainer, "overflow", "auto");
+ }
+
+ }
+
+ private void setFirstWidget(Widget w) {
+ if (firstChild != null) {
+ firstChild.removeFromParent();
+ }
+ super.add(w, firstContainer);
+ firstChild = w;
+ }
+
+ private void setSecondWidget(Widget w) {
+ if (secondChild != null) {
+ secondChild.removeFromParent();
+ }
+ super.add(w, secondContainer);
+ secondChild = w;
+ }
+
+ public void setHeight(String height) {
+ super.setHeight(height);
+ // give sane height
+ getOffsetHeight(); // shake IE
+ if (getOffsetHeight() < MIN_SIZE) {
+ super.setHeight(MIN_SIZE + "px");
+ }
+ }
+
+ public void setWidth(String width) {
+ super.setWidth(width);
+ // give sane width
+ getOffsetWidth(); // shake IE
+ if (getOffsetWidth() < MIN_SIZE) {
+ super.setWidth(MIN_SIZE + "px");
+ }
+ }
+
+ public void onBrowserEvent(Event event) {
+ switch (DOM.eventGetType(event)) {
+ case Event.ONMOUSEMOVE:
+ if (resizing) {
+ onMouseMove(event);
+ }
+ break;
+ case Event.ONMOUSEDOWN:
+ onMouseDown(event);
+ break;
+ case Event.ONMOUSEUP:
+ if (resizing) {
+ onMouseUp(event);
+ }
+ break;
+ case Event.ONCLICK:
+ resizing = false;
+ break;
+ }
+ }
+
+ public void onMouseDown(Event event) {
+ if (locked) {
+ return;
+ }
+ final Element trg = DOM.eventGetTarget(event);
+ if (DOM.compare(trg, splitter)
+ || DOM.compare(trg, DOM.getChild(splitter, 0))) {
+ resizing = true;
+ DOM.setCapture(getElement());
+ origX = DOM.getElementPropertyInt(splitter, "offsetLeft");
+ origY = DOM.getElementPropertyInt(splitter, "offsetTop");
+ origMouseX = DOM.eventGetClientX(event);
+ origMouseY = DOM.eventGetClientY(event);
+ DOM.eventCancelBubble(event, true);
+ DOM.eventPreventDefault(event);
+ }
+ }
+
+ public void onMouseMove(Event event) {
+ switch (orientation) {
+ case ORIENTATION_HORIZONTAL:
+ final int x = DOM.eventGetClientX(event);
+ onHorizontalMouseMove(x);
+ break;
+ case ORIENTATION_VERTICAL:
+ default:
+ final int y = DOM.eventGetClientY(event);
+ onVerticalMouseMove(y);
+ break;
+ }
+ iLayout();
+ }
+
+ private void onHorizontalMouseMove(int x) {
+ int newX = origX + x - origMouseX;
+ if (newX < 0) {
+ newX = 0;
+ }
+ if (newX + getSplitterSize() > getOffsetWidth()) {
+ newX = getOffsetWidth() - getSplitterSize();
+ }
+ DOM.setStyleAttribute(splitter, "left", newX + "px");
+ }
+
+ private void onVerticalMouseMove(int y) {
+ int newY = origY + y - origMouseY;
+ if (newY < 0) {
+ newY = 0;
+ }
+
+ if (newY + getSplitterSize() > getOffsetHeight()) {
+ newY = getOffsetHeight() - getSplitterSize();
+ }
+ DOM.setStyleAttribute(splitter, "top", newY + "px");
+ }
+
+ public void onMouseUp(Event event) {
+ DOM.releaseCapture(getElement());
+ resizing = false;
+ onMouseMove(event);
+ }
+
+ private static int splitterSize = -1;
+
+ private int getSplitterSize() {
+ if (splitterSize < 0) {
+ if (isAttached()) {
+ switch (orientation) {
+ case ORIENTATION_HORIZONTAL:
+ splitterSize = DOM.getElementPropertyInt(splitter,
+ "offsetWidth");
+ break;
+
+ default:
+ splitterSize = DOM.getElementPropertyInt(splitter,
+ "offsetHeight");
+ break;
+ }
+ }
+ }
+ return splitterSize;
+ }
+
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client.ui;
+
+public class ISplitPanelHorizontal extends ISplitPanel {
+
+ public ISplitPanelHorizontal() {
+ super(ISplitPanel.ORIENTATION_HORIZONTAL);
+ }
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client.ui;
+
+public class ISplitPanelVertical extends ISplitPanel {
+
+ public ISplitPanelVertical() {
+ super(ISplitPanel.ORIENTATION_VERTICAL);
+ }
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client.ui;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.Vector;
+
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.ClickListener;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.Grid;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.google.gwt.user.client.ui.VerticalPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;
+import com.itmill.toolkit.terminal.gwt.client.Paintable;
+import com.itmill.toolkit.terminal.gwt.client.UIDL;
+
+/**
+ * TODO make this work (just an early prototype). We may want to have paging
+ * style table which will be much lighter than IScrollTable is.
+ */
+public class ITablePaging extends Composite implements Table, Paintable,
+ ClickListener {
+
+ private final Grid tBody = new Grid();
+ private final Button nextPage = new Button(">");
+ private final Button prevPage = new Button("<");
+ private final Button firstPage = new Button("<<");
+ private final Button lastPage = new Button(">>");
+
+ private int pageLength = 15;
+
+ private boolean rowHeaders = false;
+
+ private ApplicationConnection client;
+ private String id;
+
+ private boolean immediate = false;
+
+ private int selectMode = Table.SELECT_MODE_NONE;
+
+ private final Vector selectedRowKeys = new Vector();
+
+ private int totalRows;
+
+ private final HashMap visibleColumns = new HashMap();
+
+ private int rows;
+
+ private int firstRow;
+ private boolean sortAscending = true;
+ private final HorizontalPanel pager;
+
+ public HashMap rowKeysToTableRows = new HashMap();
+
+ public ITablePaging() {
+
+ tBody.setStyleName("itable-tbody");
+
+ final VerticalPanel panel = new VerticalPanel();
+
+ pager = new HorizontalPanel();
+ pager.add(firstPage);
+ firstPage.addClickListener(this);
+ pager.add(prevPage);
+ prevPage.addClickListener(this);
+ pager.add(nextPage);
+ nextPage.addClickListener(this);
+ pager.add(lastPage);
+ lastPage.addClickListener(this);
+
+ panel.add(pager);
+ panel.add(tBody);
+
+ initWidget(panel);
+ }
+
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+ if (client.updateComponent(this, uidl, true)) {
+ return;
+ }
+
+ this.client = client;
+ id = uidl.getStringAttribute("id");
+ immediate = uidl.getBooleanAttribute("immediate");
+ totalRows = uidl.getIntAttribute("totalrows");
+ pageLength = uidl.getIntAttribute("pagelength");
+ firstRow = uidl.getIntAttribute("firstrow");
+ rows = uidl.getIntAttribute("rows");
+
+ if (uidl.hasAttribute("selectmode")) {
+ if (uidl.getStringAttribute("selectmode").equals("multi")) {
+ selectMode = Table.SELECT_MODE_MULTI;
+ } else {
+ selectMode = Table.SELECT_MODE_SINGLE;
+ }
+
+ if (uidl.hasAttribute("selected")) {
+ final Set selectedKeys = uidl
+ .getStringArrayVariableAsSet("selected");
+ selectedRowKeys.clear();
+ for (final Iterator it = selectedKeys.iterator(); it.hasNext();) {
+ selectedRowKeys.add(it.next());
+ }
+ }
+ }
+
+ if (uidl.hasVariable("sortascending")) {
+ sortAscending = uidl.getBooleanVariable("sortascending");
+ }
+
+ if (uidl.hasAttribute("rowheaders")) {
+ rowHeaders = true;
+ }
+
+ UIDL rowData = null;
+ UIDL visibleColumns = null;
+ for (final Iterator it = uidl.getChildIterator(); it.hasNext();) {
+ final UIDL c = (UIDL) it.next();
+ if (c.getTag().equals("rows")) {
+ rowData = c;
+ } else if (c.getTag().equals("actions")) {
+ updateActionMap(c);
+ } else if (c.getTag().equals("visiblecolumns")) {
+ visibleColumns = c;
+ }
+ }
+ tBody.resize(rows + 1, uidl.getIntAttribute("cols")
+ + (rowHeaders ? 1 : 0));
+ updateHeader(visibleColumns);
+ updateBody(rowData);
+
+ updatePager();
+ }
+
+ private void updateHeader(UIDL c) {
+ final Iterator it = c.getChildIterator();
+ visibleColumns.clear();
+ int colIndex = (rowHeaders ? 1 : 0);
+ while (it.hasNext()) {
+ final UIDL col = (UIDL) it.next();
+ final String cid = col.getStringAttribute("cid");
+ if (!col.hasAttribute("collapsed")) {
+ tBody.setWidget(0, colIndex, new HeaderCell(cid, col
+ .getStringAttribute("caption")));
+
+ }
+ colIndex++;
+ }
+ }
+
+ private void updateActionMap(UIDL c) {
+ // TODO Auto-generated method stub
+
+ }
+
+ /**
+ * Updates row data from uidl. UpdateFromUIDL delegates updating tBody to
+ * this method.
+ *
+ * Updates may be to different part of tBody, depending on update type. It
+ * can be initial row data, scroll up, scroll down...
+ *
+ * @param uidl
+ * which contains row data
+ */
+ private void updateBody(UIDL uidl) {
+ final Iterator it = uidl.getChildIterator();
+
+ int curRowIndex = 1;
+ while (it.hasNext()) {
+ final UIDL rowUidl = (UIDL) it.next();
+ final TableRow row = new TableRow(curRowIndex, String
+ .valueOf(rowUidl.getIntAttribute("key")), rowUidl
+ .hasAttribute("selected"));
+ int colIndex = 0;
+ if (rowHeaders) {
+ tBody.setWidget(curRowIndex, colIndex, new BodyCell(row,
+ rowUidl.getStringAttribute("caption")));
+ colIndex++;
+ }
+ final Iterator cells = rowUidl.getChildIterator();
+ while (cells.hasNext()) {
+ final Object cell = cells.next();
+ if (cell instanceof String) {
+ tBody.setWidget(curRowIndex, colIndex, new BodyCell(row,
+ (String) cell));
+ } else {
+ final Paintable cellContent = client
+ .getPaintable((UIDL) cell);
+ final BodyCell bodyCell = new BodyCell(row);
+ bodyCell.setWidget((Widget) cellContent);
+ tBody.setWidget(curRowIndex, colIndex, bodyCell);
+ }
+ colIndex++;
+ }
+ curRowIndex++;
+ }
+ }
+
+ private void updatePager() {
+ if (pageLength == 0) {
+ pager.setVisible(false);
+ return;
+ }
+ if (isFirstPage()) {
+ firstPage.setEnabled(false);
+ prevPage.setEnabled(false);
+ } else {
+ firstPage.setEnabled(true);
+ prevPage.setEnabled(true);
+ }
+ if (hasNextPage()) {
+ nextPage.setEnabled(true);
+ lastPage.setEnabled(true);
+ } else {
+ nextPage.setEnabled(false);
+ lastPage.setEnabled(false);
+
+ }
+ }
+
+ private boolean hasNextPage() {
+ if (firstRow + rows + 1 > totalRows) {
+ return false;
+ }
+ return true;
+ }
+
+ private boolean isFirstPage() {
+ if (firstRow == 0) {
+ return true;
+ }
+ return false;
+ }
+
+ public void onClick(Widget sender) {
+ if (sender instanceof Button) {
+ if (sender == firstPage) {
+ client.updateVariable(id, "firstvisible", 0, true);
+ } else if (sender == nextPage) {
+ client.updateVariable(id, "firstvisible",
+ firstRow + pageLength, true);
+ } else if (sender == prevPage) {
+ int newFirst = firstRow - pageLength;
+ if (newFirst < 0) {
+ newFirst = 0;
+ }
+ client.updateVariable(id, "firstvisible", newFirst, true);
+ } else if (sender == lastPage) {
+ client.updateVariable(id, "firstvisible", totalRows
+ - pageLength, true);
+ }
+ }
+ if (sender instanceof HeaderCell) {
+ final HeaderCell hCell = (HeaderCell) sender;
+ client.updateVariable(id, "sortcolumn", hCell.getCid(), false);
+ client.updateVariable(id, "sortascending", (sortAscending ? false
+ : true), true);
+ }
+ }
+
+ private class HeaderCell extends HTML {
+
+ private String cid;
+
+ public String getCid() {
+ return cid;
+ }
+
+ public void setCid(String pid) {
+ cid = pid;
+ }
+
+ HeaderCell(String pid, String caption) {
+ super();
+ cid = pid;
+ addClickListener(ITablePaging.this);
+ setText(caption);
+ // TODO remove debug color
+ DOM.setStyleAttribute(getElement(), "color", "brown");
+ DOM.setStyleAttribute(getElement(), "font-weight", "bold");
+ }
+ }
+
+ /**
+ * Abstraction of table cell content. In needs to know on which row it is in
+ * case of context click.
+ *
+ * @author mattitahvonen
+ */
+ public class BodyCell extends SimplePanel {
+ private final TableRow row;
+
+ public BodyCell(TableRow row) {
+ super();
+ sinkEvents(Event.BUTTON_LEFT | Event.BUTTON_RIGHT);
+ this.row = row;
+ }
+
+ public BodyCell(TableRow row2, String textContent) {
+ super();
+ sinkEvents(Event.BUTTON_LEFT | Event.BUTTON_RIGHT);
+ row = row2;
+ setWidget(new Label(textContent));
+ }
+
+ public void onBrowserEvent(Event event) {
+ System.out.println("CEll event: " + event.toString());
+ switch (DOM.eventGetType(event)) {
+ case Event.BUTTON_RIGHT:
+ row.showContextMenu(event);
+ Window.alert("context menu un-implemented");
+ DOM.eventCancelBubble(event, true);
+ break;
+ case Event.BUTTON_LEFT:
+ if (selectMode > Table.SELECT_MODE_NONE) {
+ row.toggleSelected();
+ }
+ break;
+ default:
+ break;
+ }
+ super.onBrowserEvent(event);
+ }
+ }
+
+ private class TableRow {
+
+ private final String key;
+ private final int rowIndex;
+ private boolean selected = false;
+
+ public TableRow(int rowIndex, String rowKey, boolean selected) {
+ rowKeysToTableRows.put(rowKey, this);
+ this.rowIndex = rowIndex;
+ key = rowKey;
+ setSelected(selected);
+ }
+
+ /**
+ * This method is used to set row status. Does not change value on
+ * server.
+ *
+ * @param selected
+ */
+ public void setSelected(boolean sel) {
+ selected = sel;
+ if (selected) {
+ selectedRowKeys.add(key);
+ DOM.setStyleAttribute(tBody.getRowFormatter().getElement(
+ rowIndex), "background", "yellow");
+
+ } else {
+ selectedRowKeys.remove(key);
+ DOM.setStyleAttribute(tBody.getRowFormatter().getElement(
+ rowIndex), "background", "transparent");
+ }
+ }
+
+ public void setContextMenuOptions(HashMap options) {
+
+ }
+
+ /**
+ * Toggles rows select state. Also updates state to server according to
+ * tables immediate flag.
+ *
+ */
+ public void toggleSelected() {
+ if (selected) {
+ setSelected(false);
+ } else {
+ if (selectMode == Table.SELECT_MODE_SINGLE) {
+ deselectAll();
+ }
+ setSelected(true);
+ }
+ client.updateVariable(id, "selected", selectedRowKeys.toArray(),
+ immediate);
+ }
+
+ /**
+ * Shows context menu for this row.
+ *
+ * @param event
+ * Event which triggered context menu. Correct place for
+ * context menu can be determined with it.
+ */
+ public void showContextMenu(Event event) {
+ System.out.println("TODO: Show context menu");
+ }
+ }
+
+ public void deselectAll() {
+ final Object[] keys = selectedRowKeys.toArray();
+ for (int i = 0; i < keys.length; i++) {
+ final TableRow tableRow = (TableRow) rowKeysToTableRows
+ .get(keys[i]);
+ if (tableRow != null) {
+ tableRow.setSelected(false);
+ }
+ }
+ // still ensure all selects are removed from
+ selectedRowKeys.clear();
+ }
+
+ public void add(Widget w) {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void clear() {
+ // TODO Auto-generated method stub
+
+ }
+
+ public Iterator iterator() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public boolean remove(Widget w) {
+ // TODO Auto-generated method stub
+ return false;
+ }
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client.ui;
+
+import java.util.Iterator;
+
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.DeferredCommand;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.SourcesTabEvents;
+import com.google.gwt.user.client.ui.TabBar;
+import com.google.gwt.user.client.ui.TabListener;
+import com.google.gwt.user.client.ui.Widget;
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;
+import com.itmill.toolkit.terminal.gwt.client.Caption;
+import com.itmill.toolkit.terminal.gwt.client.ContainerResizedListener;
+import com.itmill.toolkit.terminal.gwt.client.Paintable;
+import com.itmill.toolkit.terminal.gwt.client.UIDL;
+import com.itmill.toolkit.terminal.gwt.client.Util;
+
+public class ITabsheet extends ITabsheetBase implements
+ ContainerResizedListener {
+
+ public static final String CLASSNAME = "i-tabsheet";
+
+ public static final String TABS_CLASSNAME = "i-tabsheet-tabcontainer";
+ public static final String SCROLLER_CLASSNAME = "i-tabsheet-scroller";
+ private final Element tabs; // tabbar and 'scroller' container
+ private final Element scroller; // tab-scroller element
+ private final Element scrollerNext; // tab-scroller next button element
+ private final Element scrollerPrev; // tab-scroller prev button element
+ private int scrollerIndex = 0;
+
+ private final TabBar tb;
+ private final ITabsheetPanel tp;
+ private final Element contentNode, deco;
+
+ private String height;
+ private String width;
+
+ private boolean waitingForResponse;
+
+ /**
+ * Previous visible widget is set invisible with CSS (not display: none, but
+ * visibility: hidden), to avoid flickering during render process. Normal
+ * visibility must be returned later when new widget is rendered.
+ */
+ private Widget previousVisibleWidget;
+
+ private final TabListener tl = new TabListener() {
+
+ public void onTabSelected(SourcesTabEvents sender, final int tabIndex) {
+ if (client != null && activeTabIndex != tabIndex) {
+ addStyleDependentName("loading");
+ // run updating variables in deferred command to bypass some
+ // FF
+ // optimization issues
+ DeferredCommand.addCommand(new Command() {
+ public void execute() {
+ previousVisibleWidget = tp.getWidget(tp
+ .getVisibleWidget());
+ DOM.setStyleAttribute(previousVisibleWidget
+ .getElement(), "visibility", "hidden");
+ client.updateVariable(id, "selected", tabKeys.get(
+ tabIndex).toString(), true);
+ }
+ });
+ waitingForResponse = true;
+ }
+ }
+
+ public boolean onBeforeTabSelected(SourcesTabEvents sender, int tabIndex) {
+ if (disabled || waitingForResponse) {
+ return false;
+ }
+ final Object tabKey = tabKeys.get(tabIndex);
+ if (disabledTabKeys.contains(tabKey)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ };
+
+ public ITabsheet() {
+ super(CLASSNAME);
+
+ // Tab scrolling
+ DOM.setStyleAttribute(getElement(), "overflow", "hidden");
+ tabs = DOM.createDiv();
+ DOM.setElementProperty(tabs, "className", TABS_CLASSNAME);
+ scroller = DOM.createDiv();
+
+ DOM.setElementProperty(scroller, "className", SCROLLER_CLASSNAME);
+ scrollerPrev = DOM.createButton();
+ DOM.setElementProperty(scrollerPrev, "className", SCROLLER_CLASSNAME
+ + "Prev");
+ DOM.sinkEvents(scrollerPrev, Event.ONCLICK);
+ scrollerNext = DOM.createButton();
+ DOM.setElementProperty(scrollerNext, "className", SCROLLER_CLASSNAME
+ + "Next");
+ DOM.sinkEvents(scrollerNext, Event.ONCLICK);
+ DOM.appendChild(getElement(), tabs);
+
+ // Tabs
+ tb = new TabBar();
+ tp = new ITabsheetPanel();
+ tp.setStyleName(CLASSNAME + "-tabsheetpanel");
+ contentNode = DOM.createDiv();
+
+ deco = DOM.createDiv();
+
+ addStyleDependentName("loading"); // Indicate initial progress
+ tb.setStyleName(CLASSNAME + "-tabs");
+ DOM
+ .setElementProperty(contentNode, "className", CLASSNAME
+ + "-content");
+ DOM.setElementProperty(deco, "className", CLASSNAME + "-deco");
+
+ add(tb, tabs);
+ DOM.appendChild(scroller, scrollerPrev);
+ DOM.appendChild(scroller, scrollerNext);
+
+ DOM.appendChild(getElement(), contentNode);
+ add(tp, contentNode);
+ DOM.appendChild(getElement(), deco);
+
+ DOM.appendChild(tabs, scroller);
+
+ tb.addTabListener(tl);
+
+ // TODO Use for Safari only. Fix annoying 1px first cell in TabBar.
+ DOM.setStyleAttribute(DOM.getFirstChild(DOM.getFirstChild(DOM
+ .getFirstChild(tb.getElement()))), "display", "none");
+
+ }
+
+ public void onBrowserEvent(Event event) {
+
+ // Tab scrolling
+ if (isScrolledTabs()
+ && DOM.compare(DOM.eventGetTarget(event), scrollerPrev)) {
+ if (scrollerIndex > 0) {
+ DOM.setStyleAttribute(DOM.getChild(DOM.getFirstChild(DOM
+ .getFirstChild(tb.getElement())), scrollerIndex),
+ "display", "");
+ scrollerIndex--;
+ updateTabScroller();
+ }
+ } else if (isClippedTabs()
+ && DOM.compare(DOM.eventGetTarget(event), scrollerNext)) {
+ int tabs = tb.getTabCount();
+ if (scrollerIndex + 1 <= tabs) {
+ scrollerIndex++;
+ DOM.setStyleAttribute(DOM.getChild(DOM.getFirstChild(DOM
+ .getFirstChild(tb.getElement())), scrollerIndex),
+ "display", "none");
+ updateTabScroller();
+ }
+ } else {
+ super.onBrowserEvent(event);
+ }
+ }
+
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+ super.updateFromUIDL(uidl, client);
+
+ // Add proper stylenames for all elements (easier to prevent unwanted
+ // style inheritance)
+ if (uidl.hasAttribute("style")) {
+ final String[] styles = uidl.getStringAttribute("style").split(" ");
+ final String contentBaseClass = CLASSNAME + "-content";
+ String contentClass = contentBaseClass;
+ final String decoBaseClass = CLASSNAME + "-deco";
+ String decoClass = decoBaseClass;
+ for (int i = 0; i < styles.length; i++) {
+ tb.addStyleDependentName(styles[i]);
+ contentClass += " " + contentBaseClass + "-" + styles[i];
+ decoClass += " " + decoBaseClass + "-" + styles[i];
+ }
+ DOM.setElementProperty(contentNode, "className", contentClass);
+ DOM.setElementProperty(deco, "className", decoClass);
+ } else {
+ tb.setStyleName(CLASSNAME + "-tabs");
+ DOM.setElementProperty(contentNode, "className", CLASSNAME
+ + "-content");
+ DOM.setElementProperty(deco, "className", CLASSNAME + "-deco");
+ }
+
+ if (uidl.hasAttribute("hidetabs")) {
+ tb.setVisible(false);
+ addStyleName(CLASSNAME + "-hidetabs");
+ } else {
+ tb.setVisible(true);
+ removeStyleName(CLASSNAME + "-hidetabs");
+ }
+
+ // tabs; push or not
+ if (uidl.hasAttribute("width")) {
+ // update width later, in updateTabScroller();
+ DOM.setStyleAttribute(tabs, "width", "1px");
+ DOM.setStyleAttribute(tabs, "overflow", "hidden");
+ } else {
+ showAllTabs();
+ DOM.setStyleAttribute(tabs, "width", "");
+ DOM.setStyleAttribute(tabs, "overflow", "visible");
+ }
+
+ updateTabScroller();
+ waitingForResponse = false;
+ }
+
+ protected void renderTab(final UIDL tabUidl, int index, boolean selected) {
+ // TODO check indexes, now new tabs get placed last (changing tab order
+ // is not supported from server-side)
+ Caption c = new Caption(null, client);
+ c.updateCaption(tabUidl);
+ tb.addTab(c);
+ if (selected) {
+ renderContent(tabUidl.getChildUIDL(0));
+ tb.selectTab(index);
+ } else if (tabUidl.getChildCount() > 0) {
+ // updating a drawn child on hidden tab
+ Paintable paintable = client.getPaintable(tabUidl.getChildUIDL(0));
+ paintable.updateFromUIDL(tabUidl.getChildUIDL(0), client);
+ }
+ // Add place-holder content
+ tp.add(new Label(""));
+ }
+
+ protected void selectTab(int index, final UIDL contentUidl) {
+ if (index != activeTabIndex) {
+ activeTabIndex = index;
+ tb.selectTab(activeTabIndex);
+ }
+ renderContent(contentUidl);
+ }
+
+ private void renderContent(final UIDL contentUIDL) {
+ final Paintable content = client.getPaintable(contentUIDL);
+ if (tp.getWidgetCount() > activeTabIndex) {
+ Widget old = tp.getWidget(activeTabIndex);
+ if (old != content) {
+ tp.remove(activeTabIndex);
+ if (old instanceof Paintable) {
+ client.unregisterPaintable((Paintable) old);
+ }
+ tp.insert((Widget) content, activeTabIndex);
+ }
+ } else {
+ tp.add((Widget) content);
+ }
+
+ tp.showWidget(activeTabIndex);
+
+ ITabsheet.this.iLayout();
+ (content).updateFromUIDL(contentUIDL, client);
+ ITabsheet.this.removeStyleDependentName("loading");
+ if (previousVisibleWidget != null) {
+ DOM.setStyleAttribute(previousVisibleWidget.getElement(),
+ "visibility", "");
+ previousVisibleWidget = null;
+ }
+ }
+
+ public void setHeight(String height) {
+ if (this.height == null && height == null) {
+ return;
+ }
+ String oldHeight = this.height;
+ this.height = height;
+ if ((this.height != null && height == null)
+ || (this.height == null && height != null)
+ || !height.equals(oldHeight)) {
+ iLayout();
+ }
+ }
+
+ public void setWidth(String width) {
+ String oldWidth = this.width;
+ this.width = width;
+ if ("100%".equals(width)) {
+ // Allow browser to calculate width
+ super.setWidth("");
+ } else {
+ super.setWidth(width);
+ }
+ if ((this.width != null && width == null)
+ || (this.width == null && width != null)
+ || !width.equals(oldWidth)) {
+ // Run descendant layout functions
+ Util.runDescendentsLayout(this);
+ }
+ }
+
+ public void iLayout() {
+ if (height != null && height != "") {
+ super.setHeight(height);
+
+ final int contentHeight = getOffsetHeight()
+ - DOM.getElementPropertyInt(deco, "offsetHeight")
+ - tb.getOffsetHeight();
+
+ // Set proper values for content element
+ DOM.setStyleAttribute(contentNode, "height", contentHeight + "px");
+ DOM.setStyleAttribute(contentNode, "overflow", "auto");
+ tp.setHeight("100%");
+
+ } else {
+ DOM.setStyleAttribute(contentNode, "height", "");
+ DOM.setStyleAttribute(contentNode, "overflow", "");
+ }
+ Util.runDescendentsLayout(this);
+
+ updateTabScroller();
+ }
+
+ /**
+ * Layouts the tab-scroller elements, and applies styles.
+ */
+ private void updateTabScroller() {
+ if (width != null) {
+ DOM.setStyleAttribute(tabs, "width", width);
+ }
+ if (scrollerIndex > tb.getTabCount()) {
+ scrollerIndex = 0;
+ }
+ boolean scrolled = isScrolledTabs();
+ boolean clipped = isClippedTabs();
+ if (tb.isVisible() && (scrolled || clipped)) {
+ DOM.setStyleAttribute(scroller, "display", "");
+ DOM.setElementProperty(scrollerPrev, "className",
+ SCROLLER_CLASSNAME + (scrolled ? "Prev" : "Prev-disabled"));
+ DOM.setElementProperty(scrollerNext, "className",
+ SCROLLER_CLASSNAME + (clipped ? "Next" : "Next-disabled"));
+ } else {
+ DOM.setStyleAttribute(scroller, "display", "none");
+ }
+
+ }
+
+ private void showAllTabs() {
+ scrollerIndex = 0;
+ for (int i = 0; i < tb.getTabCount(); i++) {
+ DOM.setStyleAttribute(DOM.getChild(DOM.getFirstChild(DOM
+ .getFirstChild(tb.getElement())), i + 1), "display", "");
+ }
+ }
+
+ private boolean isScrolledTabs() {
+ return scrollerIndex > 0;
+ }
+
+ private boolean isClippedTabs() {
+ return tb.getOffsetWidth() > getOffsetWidth();
+ }
+
+ protected void clearPaintables() {
+
+ int i = tb.getTabCount();
+ while (i > 0) {
+ tb.removeTab(--i);
+ }
+ tp.clear();
+
+ // Get rid of unnecessary 100% cell heights in TabBar (really ugly hack)
+ final Element tr = DOM.getChild(DOM.getChild(tb.getElement(), 0), 0);
+ final Element rest = DOM.getChild(DOM.getChild(tr, DOM
+ .getChildCount(tr) - 1), 0);
+ DOM.removeElementAttribute(rest, "style");
+
+ }
+
+ protected Iterator getPaintableIterator() {
+ return tp.iterator();
+ }
+}
--- /dev/null
+package com.itmill.toolkit.terminal.gwt.client.ui;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.ui.ComplexPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;
+import com.itmill.toolkit.terminal.gwt.client.Paintable;
+import com.itmill.toolkit.terminal.gwt.client.UIDL;
+
+abstract class ITabsheetBase extends ComplexPanel implements Paintable {
+
+ String id;
+ ApplicationConnection client;
+
+ protected final ArrayList tabKeys = new ArrayList();
+ protected final ArrayList captions = new ArrayList();
+ protected int activeTabIndex = 0;
+ protected boolean disabled;
+ protected boolean readonly;
+ protected Set disabledTabKeys = new HashSet();
+
+ public ITabsheetBase(String classname) {
+ setElement(DOM.createDiv());
+ setStylePrimaryName(classname);
+ }
+
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+
+ // Ensure correct implementation
+ if (client.updateComponent(this, uidl, true)) {
+ return;
+ }
+
+ // Update member references
+ this.client = client;
+ id = uidl.getId();
+ disabled = uidl.hasAttribute("disabled");
+
+ // Render content
+ final UIDL tabs = uidl.getChildUIDL(0);
+ if (keepCurrentTabs(uidl)) {
+ int index = 0;
+ for (final Iterator it = tabs.getChildIterator(); it.hasNext();) {
+ final UIDL tab = (UIDL) it.next();
+ final boolean selected = tab.getBooleanAttribute("selected");
+ if (selected) {
+ selectTab(index, tab.getChildUIDL(0));
+ } else if (tab.getChildCount() > 0) {
+ // updating a drawn child on hidden tab
+ Paintable paintable = client.getPaintable(tab
+ .getChildUIDL(0));
+ // TODO widget may flash on screen
+ paintable.updateFromUIDL(tab.getChildUIDL(0), client);
+ // Hack #1 in ITabsheetBase: due ITabsheets content has no
+ // wrappers for each tab, we need to hide the actual widgets
+ ((Widget) paintable).setVisible(false);
+ }
+ index++;
+ }
+ } else {
+
+ ArrayList oldPaintables = new ArrayList();
+ for (Iterator iterator = getPaintableIterator(); iterator.hasNext();) {
+ oldPaintables.add(iterator.next());
+ }
+
+ // Clear previous values
+ tabKeys.clear();
+ captions.clear();
+ disabledTabKeys.clear();
+
+ clearPaintables();
+
+ int index = 0;
+ for (final Iterator it = tabs.getChildIterator(); it.hasNext();) {
+ final UIDL tab = (UIDL) it.next();
+ final String key = tab.getStringAttribute("key");
+ final boolean selected = tab.getBooleanAttribute("selected");
+ String caption = tab.getStringAttribute("caption");
+ if (caption == null) {
+ caption = " ";
+ }
+
+ if (tab.getBooleanAttribute("disabled")) {
+ disabledTabKeys.add(key);
+ }
+
+ captions.add(caption);
+ tabKeys.add(key);
+
+ if (selected) {
+ activeTabIndex = index;
+ }
+ if (tab.getChildCount() > 0) {
+ Paintable p = client.getPaintable(tab.getChildUIDL(0));
+ oldPaintables.remove(p);
+ }
+ renderTab(tab, index, selected);
+ index++;
+ }
+
+ for (Iterator iterator = oldPaintables.iterator(); iterator
+ .hasNext();) {
+ Object oldPaintable = iterator.next();
+ if (oldPaintable instanceof Paintable) {
+ client.unregisterPaintable((Paintable) oldPaintable);
+ }
+ }
+ }
+
+ }
+
+ /**
+ * @return a list of currently shown Paintables
+ */
+ abstract protected Iterator getPaintableIterator();
+
+ /**
+ * Clears current tabs and contents
+ */
+ abstract protected void clearPaintables();
+
+ protected boolean keepCurrentTabs(UIDL uidl) {
+ final UIDL tabs = uidl.getChildUIDL(0);
+ boolean retval = tabKeys.size() == tabs.getNumberOfChildren();
+ for (int i = 0; retval && i < tabKeys.size(); i++) {
+ String key = (String) tabKeys.get(i);
+ UIDL tabUIDL = tabs.getChildUIDL(i);
+ retval = key.equals(tabUIDL.getStringAttribute("key"))
+ && captions.get(i).equals(
+ tabUIDL.getStringAttribute("caption"))
+ && (tabUIDL.hasAttribute("disabled") == disabledTabKeys
+ .contains(key));
+ }
+ return retval;
+ }
+
+ /**
+ * Implement in extending classes. This method should render needed elements
+ * and set the visibility of the tab according to the 'selected' parameter.
+ */
+ protected abstract void renderTab(final UIDL tabUidl, int index,
+ boolean selected);
+
+ /**
+ * Implement in extending classes. This method should render any previously
+ * non-cached content and set the activeTabIndex property to the specified
+ * index.
+ */
+ protected abstract void selectTab(int index, final UIDL contentUidl);
+
+}
--- /dev/null
+/* \r
+@ITMillApache2LicenseForJavaFiles@\r
+ */\r
+\r
+package com.itmill.toolkit.terminal.gwt.client.ui;\r
+\r
+import com.google.gwt.user.client.DOM;\r
+import com.google.gwt.user.client.ui.ComplexPanel;\r
+import com.google.gwt.user.client.ui.Widget;\r
+\r
+/**\r
+ * A panel that displays all of its child widgets in a 'deck', where only one\r
+ * can be visible at a time. It is used by\r
+ * {@link com.itmill.toolkit.terminal.gwt.client.ui.ITabsheetPanel}.\r
+ * \r
+ * This class has the same basic functionality as the GWT DeckPanel\r
+ * {@link com.google.gwt.user.client.ui.DeckPanel}, with the exception that it\r
+ * doesn't manipulate the child widgets' width and height attributes.\r
+ */\r
+public class ITabsheetPanel extends ComplexPanel {\r
+\r
+ private Widget visibleWidget;\r
+\r
+ /**\r
+ * Creates an empty tabsheet panel.\r
+ */\r
+ public ITabsheetPanel() {\r
+ setElement(DOM.createDiv());\r
+ }\r
+\r
+ /**\r
+ * Adds the specified widget to the deck.\r
+ * \r
+ * @param w\r
+ * the widget to be added\r
+ */\r
+ public void add(Widget w) {\r
+ super.add(w, getElement());\r
+ initChildWidget(w);\r
+ }\r
+\r
+ /**\r
+ * Gets the index of the currently-visible widget.\r
+ * \r
+ * @return the visible widget's index\r
+ */\r
+ public int getVisibleWidget() {\r
+ return getWidgetIndex(visibleWidget);\r
+ }\r
+\r
+ /**\r
+ * Inserts a widget before the specified index.\r
+ * \r
+ * @param w\r
+ * the widget to be inserted\r
+ * @param beforeIndex\r
+ * the index before which it will be inserted\r
+ * @throws IndexOutOfBoundsException\r
+ * if <code>beforeIndex</code> is out of range\r
+ */\r
+ public void insert(Widget w, int beforeIndex) {\r
+ super.insert(w, getElement(), beforeIndex, true);\r
+ initChildWidget(w);\r
+ }\r
+\r
+ public boolean remove(Widget w) {\r
+ final boolean removed = super.remove(w);\r
+ if (removed) {\r
+ resetChildWidget(w);\r
+\r
+ if (visibleWidget == w) {\r
+ visibleWidget = null;\r
+ }\r
+ }\r
+ return removed;\r
+ }\r
+\r
+ /**\r
+ * Shows the widget at the specified index. This causes the currently-\r
+ * visible widget to be hidden.\r
+ * \r
+ * @param index\r
+ * the index of the widget to be shown\r
+ */\r
+ public void showWidget(int index) {\r
+ checkIndexBoundsForAccess(index);\r
+ Widget newVisible = getWidget(index);\r
+ if (visibleWidget != newVisible) {\r
+ if (visibleWidget != null) {\r
+ visibleWidget.setVisible(false);\r
+ }\r
+ visibleWidget = newVisible;\r
+ visibleWidget.setVisible(true);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Make the widget invisible, and set its width and height to full.\r
+ */\r
+ private void initChildWidget(Widget w) {\r
+ w.setVisible(false);\r
+ }\r
+\r
+ /**\r
+ * Make the widget visible, and clear the widget's width and height\r
+ * attributes. This is done so that any changes to the visibility, height,\r
+ * or width of the widget that were done by the panel are undone.\r
+ */\r
+ private void resetChildWidget(Widget w) {\r
+ w.setVisible(true);\r
+ }\r
+}\r
--- /dev/null
+/* \r
+@ITMillApache2LicenseForJavaFiles@\r
+ */\r
+\r
+package com.itmill.toolkit.terminal.gwt.client.ui;\r
+\r
+import com.google.gwt.user.client.DOM;\r
+import com.google.gwt.user.client.Element;\r
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;\r
+import com.itmill.toolkit.terminal.gwt.client.UIDL;\r
+\r
+/**\r
+ * This class represents a multiline textfield (textarea).\r
+ * \r
+ * TODO consider replacing this with a RichTextArea based implementation. IE\r
+ * does not support CSS height for textareas in Strict mode :-(\r
+ * \r
+ * @author IT Mill Ltd.\r
+ * \r
+ */\r
+public class ITextArea extends ITextField {\r
+ public static final String CLASSNAME = "i-textarea";\r
+\r
+ public ITextArea() {\r
+ super(DOM.createTextArea());\r
+ setStyleName(CLASSNAME);\r
+ }\r
+\r
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {\r
+ // Call parent renderer explicitly\r
+ super.updateFromUIDL(uidl, client);\r
+\r
+ if (uidl.hasAttribute("rows")) {\r
+ setRows(new Integer(uidl.getStringAttribute("rows")).intValue());\r
+ }\r
+ }\r
+\r
+ public void setRows(int rows) {\r
+ setRows(getElement(), rows);\r
+ }\r
+\r
+ private native void setRows(Element e, int r)\r
+ /*-{\r
+ try {\r
+ if(e.tagName.toLowerCase() == "textarea")\r
+ e.rows = r;\r
+ } catch (e) {}\r
+ }-*/;\r
+\r
+}\r
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client.ui;
+
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.ChangeListener;
+import com.google.gwt.user.client.ui.FocusListener;
+import com.google.gwt.user.client.ui.TextBoxBase;
+import com.google.gwt.user.client.ui.Widget;
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;
+import com.itmill.toolkit.terminal.gwt.client.Paintable;
+import com.itmill.toolkit.terminal.gwt.client.Tooltip;
+import com.itmill.toolkit.terminal.gwt.client.UIDL;
+
+/**
+ * This class represents a basic text input field with one row.
+ *
+ * @author IT Mill Ltd.
+ *
+ */
+public class ITextField extends TextBoxBase implements Paintable, Field,
+ ChangeListener, FocusListener {
+
+ /**
+ * The input node CSS classname.
+ */
+ public static final String CLASSNAME = "i-textfield";
+ /**
+ * This CSS classname is added to the input node on hover.
+ */
+ public static final String CLASSNAME_FOCUS = "focus";
+
+ protected String id;
+
+ protected ApplicationConnection client;
+
+ private boolean immediate = false;
+
+ public ITextField() {
+ this(DOM.createInputText());
+ }
+
+ protected ITextField(Element node) {
+ super(node);
+ setStyleName(CLASSNAME);
+ addChangeListener(this);
+ addFocusListener(this);
+ sinkEvents(Tooltip.TOOLTIP_EVENTS);
+ }
+
+ public void onBrowserEvent(Event event) {
+ super.onBrowserEvent(event);
+ if (client != null) {
+ client.handleTooltipEvent(event, this);
+ }
+ }
+
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+ this.client = client;
+ id = uidl.getId();
+
+ if (client.updateComponent(this, uidl, true)) {
+ return;
+ }
+
+ if (uidl.getBooleanAttribute("readonly")) {
+ setReadOnly(true);
+ } else {
+ setReadOnly(false);
+ }
+
+ immediate = uidl.getBooleanAttribute("immediate");
+
+ if (uidl.hasAttribute("cols")) {
+ setColumns(new Integer(uidl.getStringAttribute("cols")).intValue());
+ }
+
+ setText(uidl.getStringVariable("text"));
+
+ }
+
+ public void onChange(Widget sender) {
+ if (client != null && id != null) {
+ client.updateVariable(id, "text", getText(), immediate);
+ }
+ }
+
+ public void onFocus(Widget sender) {
+ addStyleDependentName(CLASSNAME_FOCUS);
+ }
+
+ public void onLostFocus(Widget sender) {
+ removeStyleDependentName(CLASSNAME_FOCUS);
+ }
+
+ public void setColumns(int columns) {
+ setColumns(getElement(), columns);
+ }
+
+ private native void setColumns(Element e, int c)
+ /*-{
+ try {
+ switch(e.tagName.toLowerCase()) {
+ case "input":
+ //e.size = c;
+ e.style.width = c+"em";
+ break;
+ case "textarea":
+ //e.cols = c;
+ e.style.width = c+"em";
+ break;
+ default:;
+ }
+ } catch (e) {}
+ }-*/;
+
+}
--- /dev/null
+/* \r
+@ITMillApache2LicenseForJavaFiles@\r
+ */\r
+\r
+package com.itmill.toolkit.terminal.gwt.client.ui;\r
+\r
+import java.util.Date;\r
+\r
+import com.google.gwt.i18n.client.DateTimeFormat;\r
+import com.google.gwt.user.client.ui.ChangeListener;\r
+import com.google.gwt.user.client.ui.Widget;\r
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;\r
+import com.itmill.toolkit.terminal.gwt.client.ContainerResizedListener;\r
+import com.itmill.toolkit.terminal.gwt.client.Focusable;\r
+import com.itmill.toolkit.terminal.gwt.client.LocaleNotLoadedException;\r
+import com.itmill.toolkit.terminal.gwt.client.LocaleService;\r
+import com.itmill.toolkit.terminal.gwt.client.Paintable;\r
+import com.itmill.toolkit.terminal.gwt.client.UIDL;\r
+import com.itmill.toolkit.terminal.gwt.client.Util;\r
+\r
+public class ITextualDate extends IDateField implements Paintable, Field,\r
+ ChangeListener, ContainerResizedListener, Focusable {\r
+\r
+ private static final String PARSE_ERROR_CLASSNAME = CLASSNAME\r
+ + "-parseerror";\r
+\r
+ private final ITextField text;\r
+\r
+ private String formatStr;\r
+\r
+ private String width;\r
+\r
+ private boolean needLayout;\r
+\r
+ protected int fieldExtraWidth = -1;\r
+\r
+ public ITextualDate() {\r
+ super();\r
+ text = new ITextField();\r
+ text.addChangeListener(this);\r
+ add(text);\r
+ }\r
+\r
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {\r
+\r
+ int origRes = currentResolution;\r
+ super.updateFromUIDL(uidl, client);\r
+ if (origRes != currentResolution) {\r
+ // force recreating format string\r
+ formatStr = null;\r
+ }\r
+ buildDate();\r
+ }\r
+\r
+ protected String getFormatString() {\r
+ if (formatStr == null) {\r
+ if (currentResolution == RESOLUTION_YEAR) {\r
+ formatStr = "yyyy"; // force full year\r
+ } else {\r
+\r
+ try {\r
+ String frmString = LocaleService\r
+ .getDateFormat(currentLocale);\r
+ frmString = cleanFormat(frmString);\r
+ String delim = LocaleService\r
+ .getClockDelimiter(currentLocale);\r
+\r
+ if (currentResolution >= RESOLUTION_HOUR) {\r
+ if (dts.isTwelveHourClock()) {\r
+ frmString += " hh";\r
+ } else {\r
+ frmString += " HH";\r
+ }\r
+ if (currentResolution >= RESOLUTION_MIN) {\r
+ frmString += ":mm";\r
+ if (currentResolution >= RESOLUTION_SEC) {\r
+ frmString += ":ss";\r
+ if (currentResolution >= RESOLUTION_MSEC) {\r
+ frmString += ".SSS";\r
+ }\r
+ }\r
+ }\r
+ if (dts.isTwelveHourClock()) {\r
+ frmString += " aaa";\r
+ }\r
+\r
+ }\r
+\r
+ formatStr = frmString;\r
+ } catch (LocaleNotLoadedException e) {\r
+ // TODO Auto-generated catch block\r
+ e.printStackTrace();\r
+ }\r
+ }\r
+ }\r
+ return formatStr;\r
+ }\r
+\r
+ /**\r
+ * \r
+ */\r
+ protected void buildDate() {\r
+ removeStyleName(PARSE_ERROR_CLASSNAME);\r
+ // Create the initial text for the textfield\r
+ String dateText;\r
+ if (date != null) {\r
+ dateText = DateTimeFormat.getFormat(getFormatString()).format(date);\r
+ } else {\r
+ dateText = "";\r
+ }\r
+\r
+ text.setText(dateText);\r
+ text.setEnabled(enabled && !readonly);\r
+\r
+ if (readonly) {\r
+ text.addStyleName("i-readonly");\r
+ } else {\r
+ text.removeStyleName("i-readonly");\r
+ }\r
+\r
+ }\r
+\r
+ public void onChange(Widget sender) {\r
+ if (sender == text) {\r
+ if (!text.getText().equals("")) {\r
+ try {\r
+ date = DateTimeFormat.getFormat(getFormatString()).parse(\r
+ text.getText());\r
+ // remove possibly added invalid value indication\r
+ removeStyleName(PARSE_ERROR_CLASSNAME);\r
+ } catch (final Exception e) {\r
+ ApplicationConnection.getConsole().log(e.getMessage());\r
+ addStyleName(PARSE_ERROR_CLASSNAME);\r
+ client.updateVariable(id, "lastInvalidDateString", text\r
+ .getText(), false);\r
+ date = null;\r
+ }\r
+ } else {\r
+ date = null;\r
+ // remove possibly added invalid value indication\r
+ removeStyleName(PARSE_ERROR_CLASSNAME);\r
+ }\r
+\r
+ if (date != null) {\r
+ showingDate = new Date(date.getTime());\r
+ }\r
+\r
+ // Update variables\r
+ // (only the smallest defining resolution needs to be\r
+ // immediate)\r
+ client.updateVariable(id, "year",\r
+ date != null ? date.getYear() + 1900 : -1,\r
+ currentResolution == IDateField.RESOLUTION_YEAR\r
+ && immediate);\r
+ if (currentResolution >= IDateField.RESOLUTION_MONTH) {\r
+ client.updateVariable(id, "month", date != null ? date\r
+ .getMonth() + 1 : -1,\r
+ currentResolution == IDateField.RESOLUTION_MONTH\r
+ && immediate);\r
+ }\r
+ if (currentResolution >= IDateField.RESOLUTION_DAY) {\r
+ client.updateVariable(id, "day", date != null ? date.getDate()\r
+ : -1, currentResolution == IDateField.RESOLUTION_DAY\r
+ && immediate);\r
+ }\r
+ if (currentResolution >= IDateField.RESOLUTION_HOUR) {\r
+ client.updateVariable(id, "hour", date != null ? date\r
+ .getHours() : -1,\r
+ currentResolution == IDateField.RESOLUTION_HOUR\r
+ && immediate);\r
+ }\r
+ if (currentResolution >= IDateField.RESOLUTION_MIN) {\r
+ client.updateVariable(id, "min", date != null ? date\r
+ .getMinutes() : -1,\r
+ currentResolution == IDateField.RESOLUTION_MIN\r
+ && immediate);\r
+ }\r
+ if (currentResolution >= IDateField.RESOLUTION_SEC) {\r
+ client.updateVariable(id, "sec", date != null ? date\r
+ .getSeconds() : -1,\r
+ currentResolution == IDateField.RESOLUTION_SEC\r
+ && immediate);\r
+ }\r
+ if (currentResolution == IDateField.RESOLUTION_MSEC) {\r
+ client.updateVariable(id, "msec",\r
+ date != null ? getMilliseconds() : -1, immediate);\r
+ }\r
+\r
+ }\r
+ }\r
+\r
+ private String cleanFormat(String format) {\r
+ // Remove unnecessary d & M if resolution is too low\r
+ if (currentResolution < IDateField.RESOLUTION_DAY) {\r
+ format = format.replaceAll("d", "");\r
+ }\r
+ if (currentResolution < IDateField.RESOLUTION_MONTH) {\r
+ format = format.replaceAll("M", "");\r
+ }\r
+\r
+ // Remove unsupported patterns\r
+ // TODO support for 'G', era designator (used at least in Japan)\r
+ format = format.replaceAll("[GzZwWkK]", "");\r
+\r
+ // Remove extra delimiters ('/' and '.')\r
+ while (format.startsWith("/") || format.startsWith(".")\r
+ || format.startsWith("-")) {\r
+ format = format.substring(1);\r
+ }\r
+ while (format.endsWith("/") || format.endsWith(".")\r
+ || format.endsWith("-")) {\r
+ format = format.substring(0, format.length() - 1);\r
+ }\r
+\r
+ // Remove duplicate delimiters\r
+ format = format.replaceAll("//", "/");\r
+ format = format.replaceAll("\\.\\.", ".");\r
+ format = format.replaceAll("--", "-");\r
+\r
+ return format.trim();\r
+ }\r
+\r
+ public void setWidth(String newWidth) {\r
+ if (!"".equals(newWidth) && (width == null || !newWidth.equals(width))) {\r
+ if (Util.isIE6()) {\r
+ text.setColumns(1); // in IE6 cols ~ min-width\r
+ }\r
+ needLayout = true;\r
+ width = newWidth;\r
+ super.setWidth(width);\r
+ iLayout();\r
+ if (newWidth.indexOf("%") < 0) {\r
+ needLayout = false;\r
+ }\r
+ } else {\r
+ if ("".equals(newWidth) && width != null && !"".equals(width)) {\r
+ super.setWidth("");\r
+ needLayout = true;\r
+ iLayout();\r
+ needLayout = false;\r
+ width = null;\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Returns pixels in x-axis reserved for other than textfield content.\r
+ * \r
+ * @return extra width in pixels\r
+ */\r
+ protected int getFieldExtraWidth() {\r
+ if (fieldExtraWidth < 0) {\r
+ text.setWidth("0px");\r
+ fieldExtraWidth = text.getOffsetWidth();\r
+ }\r
+ return fieldExtraWidth;\r
+ }\r
+\r
+ public void iLayout() {\r
+ if (needLayout) {\r
+ text.setWidth((getOffsetWidth() - getFieldExtraWidth()) + "px");\r
+ }\r
+ }\r
+\r
+ public void focus() {\r
+ text.setFocus(true);\r
+ }\r
+}\r
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client.ui;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;
+import com.itmill.toolkit.terminal.gwt.client.Paintable;
+import com.itmill.toolkit.terminal.gwt.client.UIDL;
+import com.itmill.toolkit.terminal.gwt.client.Util;
+
+/**
+ *
+ */
+public class ITree extends FlowPanel implements Paintable {
+
+ public static final String CLASSNAME = "i-tree";
+
+ private Set selectedIds = new HashSet();
+ private ApplicationConnection client;
+ private String paintableId;
+ private boolean selectable;
+ private boolean isMultiselect;
+
+ private final HashMap keyToNode = new HashMap();
+
+ /**
+ * This map contains captions and icon urls for actions like: * "33_c" ->
+ * "Edit" * "33_i" -> "http://dom.com/edit.png"
+ */
+ private final HashMap actionMap = new HashMap();
+
+ private boolean immediate;
+
+ private boolean isNullSelectionAllowed = true;
+
+ private boolean disabled = false;
+
+ private boolean readonly;
+
+ public ITree() {
+ super();
+ setStyleName(CLASSNAME);
+ }
+
+ private void updateActionMap(UIDL c) {
+ final Iterator it = c.getChildIterator();
+ while (it.hasNext()) {
+ final UIDL action = (UIDL) it.next();
+ final String key = action.getStringAttribute("key");
+ final String caption = action.getStringAttribute("caption");
+ actionMap.put(key + "_c", caption);
+ if (action.hasAttribute("icon")) {
+ // TODO need some uri handling ??
+ actionMap.put(key + "_i", client.translateToolkitUri(action
+ .getStringAttribute("icon")));
+ }
+ }
+
+ }
+
+ public String getActionCaption(String actionKey) {
+ return (String) actionMap.get(actionKey + "_c");
+ }
+
+ public String getActionIcon(String actionKey) {
+ return (String) actionMap.get(actionKey + "_i");
+ }
+
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+ // Ensure correct implementation and let container manage caption
+ if (client.updateComponent(this, uidl, true)) {
+ return;
+ }
+
+ this.client = client;
+
+ if (uidl.hasAttribute("partialUpdate")) {
+ handleUpdate(uidl);
+ return;
+ }
+
+ paintableId = uidl.getId();
+
+ immediate = uidl.hasAttribute("immediate");
+
+ disabled = uidl.getBooleanAttribute("disabled");
+ readonly = uidl.getBooleanAttribute("readonly");
+
+ isNullSelectionAllowed = uidl.getBooleanAttribute("nullselect");
+
+ clear();
+ for (final Iterator i = uidl.getChildIterator(); i.hasNext();) {
+ final UIDL childUidl = (UIDL) i.next();
+ if ("actions".equals(childUidl.getTag())) {
+ updateActionMap(childUidl);
+ continue;
+ }
+ final TreeNode childTree = new TreeNode();
+ this.add(childTree);
+ childTree.updateFromUIDL(childUidl, client);
+ }
+ final String selectMode = uidl.getStringAttribute("selectmode");
+ selectable = !"none".equals(selectMode);
+ isMultiselect = "multi".equals(selectMode);
+
+ selectedIds = uidl.getStringArrayVariableAsSet("selected");
+
+ }
+
+ private void handleUpdate(UIDL uidl) {
+ final TreeNode rootNode = (TreeNode) keyToNode.get(uidl
+ .getStringAttribute("rootKey"));
+ if (rootNode != null) {
+ if (!rootNode.getState()) {
+ // expanding node happened server side
+ rootNode.setState(true, false);
+ }
+ rootNode.renderChildNodes(uidl.getChildIterator());
+ }
+
+ }
+
+ public void setSelected(TreeNode treeNode, boolean selected) {
+ if (selected) {
+ if (!isMultiselect) {
+ while (selectedIds.size() > 0) {
+ final String id = (String) selectedIds.iterator().next();
+ final TreeNode oldSelection = (TreeNode) keyToNode.get(id);
+ if (oldSelection != null) {
+ // can be null if the node is not visible (parent
+ // expanded)
+ oldSelection.setSelected(false);
+ }
+ selectedIds.remove(id);
+ }
+ }
+ treeNode.setSelected(true);
+ selectedIds.add(treeNode.key);
+ } else {
+ if (!isNullSelectionAllowed) {
+ if (!isMultiselect || selectedIds.size() == 1) {
+ return;
+ }
+ }
+ selectedIds.remove(treeNode.key);
+ treeNode.setSelected(false);
+ }
+ client.updateVariable(paintableId, "selected", selectedIds.toArray(),
+ immediate);
+ }
+
+ public boolean isSelected(TreeNode treeNode) {
+ return selectedIds.contains(treeNode.key);
+ }
+
+ protected class TreeNode extends SimplePanel implements ActionOwner {
+
+ public static final String CLASSNAME = "i-tree-node";
+
+ String key;
+
+ private String[] actionKeys = null;
+
+ private boolean childrenLoaded;
+
+ private Element nodeCaptionDiv;
+
+ protected Element nodeCaptionSpan;
+
+ private FlowPanel childNodeContainer;
+
+ private boolean open;
+
+ private Icon icon;
+
+ public TreeNode() {
+ constructDom();
+ sinkEvents(Event.ONCLICK);
+ }
+
+ public void onBrowserEvent(Event event) {
+ super.onBrowserEvent(event);
+ if (disabled) {
+ return;
+ }
+ final Element target = DOM.eventGetTarget(event);
+ if (DOM.compare(getElement(), target)) {
+ // state change
+ toggleState();
+ } else if (!readonly && DOM.compare(target, nodeCaptionSpan)) {
+ // caption click = selection change
+ toggleSelection();
+ }
+
+ }
+
+ private void toggleSelection() {
+ if (selectable) {
+ ITree.this.setSelected(this, !isSelected());
+ }
+ }
+
+ private void toggleState() {
+ setState(!getState(), true);
+ }
+
+ protected void constructDom() {
+ nodeCaptionDiv = DOM.createDiv();
+ DOM.setElementProperty(nodeCaptionDiv, "className", CLASSNAME
+ + "-caption");
+ nodeCaptionSpan = DOM.createSpan();
+ DOM.appendChild(getElement(), nodeCaptionDiv);
+ DOM.appendChild(nodeCaptionDiv, nodeCaptionSpan);
+
+ childNodeContainer = new FlowPanel();
+ childNodeContainer.setStylePrimaryName(CLASSNAME + "-children");
+ setWidget(childNodeContainer);
+ }
+
+ public void onDetach() {
+ Util.removeContextMenuEvent(nodeCaptionSpan);
+ super.onDetach();
+ }
+
+ public void onAttach() {
+ attachContextMenuEvent(nodeCaptionSpan);
+ super.onAttach();
+ }
+
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+ setText(uidl.getStringAttribute("caption"));
+ key = uidl.getStringAttribute("key");
+
+ keyToNode.put(key, this);
+
+ if (uidl.hasAttribute("al")) {
+ actionKeys = uidl.getStringArrayAttribute("al");
+ }
+
+ if (uidl.getTag().equals("node")) {
+ if (uidl.getChildCount() == 0) {
+ childNodeContainer.setVisible(false);
+ } else {
+ renderChildNodes(uidl.getChildIterator());
+ childrenLoaded = true;
+ }
+ } else {
+ addStyleName(CLASSNAME + "-leaf");
+ }
+ addStyleName(CLASSNAME);
+
+ if (uidl.getBooleanAttribute("expanded") && !getState()) {
+ setState(true, false);
+ }
+
+ if (uidl.getBooleanAttribute("selected")) {
+ setSelected(true);
+ }
+
+ if (uidl.hasAttribute("icon")) {
+ if (icon == null) {
+ icon = new Icon(client);
+ DOM.insertBefore(nodeCaptionDiv, icon.getElement(),
+ nodeCaptionSpan);
+ }
+ icon.setUri(uidl.getStringAttribute("icon"));
+ } else {
+ if (icon != null) {
+ DOM.removeChild(nodeCaptionDiv, icon.getElement());
+ icon = null;
+ }
+ }
+ }
+
+ private void setState(boolean state, boolean notifyServer) {
+ if (open == state) {
+ return;
+ }
+ if (state) {
+ if (!childrenLoaded && notifyServer) {
+ client.updateVariable(paintableId, "requestChildTree",
+ true, false);
+ }
+ if (notifyServer) {
+ client.updateVariable(paintableId, "expand",
+ new String[] { key }, true);
+ }
+ addStyleName(CLASSNAME + "-expanded");
+ childNodeContainer.setVisible(true);
+ } else {
+ removeStyleName(CLASSNAME + "-expanded");
+ childNodeContainer.setVisible(false);
+ if (notifyServer) {
+ client.updateVariable(paintableId, "collapse",
+ new String[] { key }, true);
+ }
+ }
+
+ open = state;
+ }
+
+ private boolean getState() {
+ return open;
+ }
+
+ private void setText(String text) {
+ DOM.setInnerText(nodeCaptionSpan, text);
+ }
+
+ private void renderChildNodes(Iterator i) {
+ childNodeContainer.clear();
+ childNodeContainer.setVisible(true);
+ while (i.hasNext()) {
+ final UIDL childUidl = (UIDL) i.next();
+ // actions are in bit weird place, don't mix them with children,
+ // but current node's actions
+ if ("actions".equals(childUidl.getTag())) {
+ updateActionMap(childUidl);
+ continue;
+ }
+ final TreeNode childTree = new TreeNode();
+ childNodeContainer.add(childTree);
+ childTree.updateFromUIDL(childUidl, client);
+ }
+ childrenLoaded = true;
+ }
+
+ public boolean isChildrenLoaded() {
+ return childrenLoaded;
+ }
+
+ public Action[] getActions() {
+ if (actionKeys == null) {
+ return new Action[] {};
+ }
+ final Action[] actions = new Action[actionKeys.length];
+ for (int i = 0; i < actions.length; i++) {
+ final String actionKey = actionKeys[i];
+ final TreeAction a = new TreeAction(this, String.valueOf(key),
+ actionKey);
+ a.setCaption(getActionCaption(actionKey));
+ a.setIconUrl(getActionIcon(actionKey));
+ actions[i] = a;
+ }
+ return actions;
+ }
+
+ public ApplicationConnection getClient() {
+ return client;
+ }
+
+ public String getPaintableId() {
+ return paintableId;
+ }
+
+ /**
+ * Adds/removes IT Mill Toolkit spesific style name. This method ought
+ * to be called only from Tree.
+ *
+ * @param selected
+ */
+ public void setSelected(boolean selected) {
+ // add style name to caption dom structure only, not to subtree
+ setStyleName(nodeCaptionDiv, "i-tree-node-selected", selected);
+ }
+
+ public boolean isSelected() {
+ return ITree.this.isSelected(this);
+ }
+
+ public void showContextMenu(Event event) {
+ if (!readonly && !disabled) {
+ if (actionKeys != null) {
+ int left = DOM.eventGetClientX(event);
+ int top = DOM.eventGetClientY(event);
+ top += Window.getScrollTop();
+ left += Window.getScrollLeft();
+ client.getContextMenu().showAt(this, left, top);
+ }
+ DOM.eventCancelBubble(event, true);
+ }
+
+ }
+
+ private native void attachContextMenuEvent(Element el)
+ /*-{
+ var node = this;
+ el.oncontextmenu = function(e) {
+ if(!e)
+ e = $wnd.event;
+ node.@com.itmill.toolkit.terminal.gwt.client.ui.ITree.TreeNode::showContextMenu(Lcom/google/gwt/user/client/Event;)(e);
+ return false;
+ };
+ }-*/;
+
+ }
+}
--- /dev/null
+/* \r
+@ITMillApache2LicenseForJavaFiles@\r
+ */\r
+\r
+package com.itmill.toolkit.terminal.gwt.client.ui;\r
+\r
+import java.util.Iterator;\r
+import java.util.Vector;\r
+\r
+import com.google.gwt.user.client.DOM;\r
+import com.google.gwt.user.client.ui.FlowPanel;\r
+import com.google.gwt.user.client.ui.HTML;\r
+import com.google.gwt.user.client.ui.ListBox;\r
+import com.google.gwt.user.client.ui.Panel;\r
+import com.google.gwt.user.client.ui.Widget;\r
+import com.itmill.toolkit.terminal.gwt.client.UIDL;\r
+\r
+public class ITwinColSelect extends IOptionGroupBase {\r
+\r
+ private static final String CLASSNAME = "i-select-twincol";\r
+\r
+ private static final int VISIBLE_COUNT = 10;\r
+\r
+ private static final int DEFAULT_COLUMN_COUNT = 10;\r
+\r
+ private final ListBox options;\r
+\r
+ private final ListBox selections;\r
+\r
+ private final IButton add;\r
+\r
+ private final IButton remove;\r
+\r
+ private FlowPanel buttons;\r
+\r
+ private Panel panel;\r
+\r
+ private boolean widthSet = false;\r
+\r
+ public ITwinColSelect() {\r
+ super(CLASSNAME);\r
+ options = new ListBox();\r
+ options.addClickListener(this);\r
+ selections = new ListBox();\r
+ selections.addClickListener(this);\r
+ options.setVisibleItemCount(VISIBLE_COUNT);\r
+ selections.setVisibleItemCount(VISIBLE_COUNT);\r
+ options.setStyleName(CLASSNAME + "-options");\r
+ selections.setStyleName(CLASSNAME + "-selections");\r
+ buttons = new FlowPanel();\r
+ buttons.setStyleName(CLASSNAME + "-buttons");\r
+ add = new IButton();\r
+ add.setText(">>");\r
+ add.addClickListener(this);\r
+ remove = new IButton();\r
+ remove.setText("<<");\r
+ remove.addClickListener(this);\r
+ panel = ((Panel) optionsContainer);\r
+ panel.add(options);\r
+ buttons.add(add);\r
+ final HTML br = new HTML("<span/>");\r
+ br.setStyleName(CLASSNAME + "-deco");\r
+ buttons.add(br);\r
+ buttons.add(remove);\r
+ panel.add(buttons);\r
+ panel.add(selections);\r
+ }\r
+\r
+ protected void buildOptions(UIDL uidl) {\r
+ final boolean enabled = !isDisabled() && !isReadonly();\r
+ options.setMultipleSelect(isMultiselect());\r
+ selections.setMultipleSelect(isMultiselect());\r
+ options.setEnabled(enabled);\r
+ selections.setEnabled(enabled);\r
+ add.setEnabled(enabled);\r
+ remove.setEnabled(enabled);\r
+ options.clear();\r
+ selections.clear();\r
+ for (final Iterator i = uidl.getChildIterator(); i.hasNext();) {\r
+ final UIDL optionUidl = (UIDL) i.next();\r
+ if (optionUidl.hasAttribute("selected")) {\r
+ selections.addItem(optionUidl.getStringAttribute("caption"),\r
+ optionUidl.getStringAttribute("key"));\r
+ } else {\r
+ options.addItem(optionUidl.getStringAttribute("caption"),\r
+ optionUidl.getStringAttribute("key"));\r
+ }\r
+ }\r
+ if (getColumns() > 0) {\r
+ options.setWidth(getColumns() + "em");\r
+ selections.setWidth(getColumns() + "em");\r
+ optionsContainer.setWidth((getColumns() * 2 + 3) + "em");\r
+ } else if (!widthSet) {\r
+ options.setWidth(DEFAULT_COLUMN_COUNT + "em");\r
+ selections.setWidth(DEFAULT_COLUMN_COUNT + "em");\r
+ optionsContainer.setWidth((DEFAULT_COLUMN_COUNT * 2 + 2) + "em");\r
+ }\r
+ if (getRows() > 0) {\r
+ options.setVisibleItemCount(getRows());\r
+ selections.setVisibleItemCount(getRows());\r
+ }\r
+ }\r
+\r
+ protected Object[] getSelectedItems() {\r
+ final Vector selectedItemKeys = new Vector();\r
+ for (int i = 0; i < selections.getItemCount(); i++) {\r
+ selectedItemKeys.add(selections.getValue(i));\r
+ }\r
+ return selectedItemKeys.toArray();\r
+ }\r
+\r
+ private boolean[] getItemsToAdd() {\r
+ final boolean[] selectedIndexes = new boolean[options.getItemCount()];\r
+ for (int i = 0; i < options.getItemCount(); i++) {\r
+ if (options.isItemSelected(i)) {\r
+ selectedIndexes[i] = true;\r
+ } else {\r
+ selectedIndexes[i] = false;\r
+ }\r
+ }\r
+ return selectedIndexes;\r
+ }\r
+\r
+ private boolean[] getItemsToRemove() {\r
+ final boolean[] selectedIndexes = new boolean[selections.getItemCount()];\r
+ for (int i = 0; i < selections.getItemCount(); i++) {\r
+ if (selections.isItemSelected(i)) {\r
+ selectedIndexes[i] = true;\r
+ } else {\r
+ selectedIndexes[i] = false;\r
+ }\r
+ }\r
+ return selectedIndexes;\r
+ }\r
+\r
+ public void onClick(Widget sender) {\r
+ super.onClick(sender);\r
+ if (sender == add) {\r
+ final boolean[] sel = getItemsToAdd();\r
+ for (int i = 0; i < sel.length; i++) {\r
+ if (sel[i]) {\r
+ final int optionIndex = i\r
+ - (sel.length - options.getItemCount());\r
+ selectedKeys.add(options.getValue(optionIndex));\r
+\r
+ // Move selection to another column\r
+ final String text = options.getItemText(optionIndex);\r
+ final String value = options.getValue(optionIndex);\r
+ selections.addItem(text, value);\r
+ selections.setItemSelected(selections.getItemCount() - 1,\r
+ true);\r
+ options.removeItem(optionIndex);\r
+ }\r
+ }\r
+ client.updateVariable(id, "selected", selectedKeys.toArray(),\r
+ isImmediate());\r
+\r
+ } else if (sender == remove) {\r
+ final boolean[] sel = getItemsToRemove();\r
+ for (int i = 0; i < sel.length; i++) {\r
+ if (sel[i]) {\r
+ final int selectionIndex = i\r
+ - (sel.length - selections.getItemCount());\r
+ selectedKeys.remove(selections.getValue(selectionIndex));\r
+\r
+ // Move selection to another column\r
+ final String text = selections.getItemText(selectionIndex);\r
+ final String value = selections.getValue(selectionIndex);\r
+ options.addItem(text, value);\r
+ options.setItemSelected(options.getItemCount() - 1, true);\r
+ selections.removeItem(selectionIndex);\r
+ }\r
+ }\r
+ client.updateVariable(id, "selected", selectedKeys.toArray(),\r
+ isImmediate());\r
+ } else if (sender == options) {\r
+ // unselect all in other list, to avoid mistakes (i.e wrong button)\r
+ final int c = selections.getItemCount();\r
+ for (int i = 0; i < c; i++) {\r
+ selections.setItemSelected(i, false);\r
+ }\r
+ } else if (sender == selections) {\r
+ // unselect all in other list, to avoid mistakes (i.e wrong button)\r
+ final int c = options.getItemCount();\r
+ for (int i = 0; i < c; i++) {\r
+ options.setItemSelected(i, false);\r
+ }\r
+ }\r
+ }\r
+\r
+ public void setHeight(String height) {\r
+ super.setHeight(height);\r
+ if ("".equals(height)) {\r
+ options.setHeight("");\r
+ selections.setHeight("");\r
+ } else {\r
+ setFullHeightInternals();\r
+ }\r
+ }\r
+\r
+ private void setFullHeightInternals() {\r
+ options.setHeight("100%");\r
+ selections.setHeight("100%");\r
+ }\r
+\r
+ public void setWidth(String width) {\r
+ super.setWidth(width);\r
+ if (!"".equals(width) && width != null) {\r
+ setRelativeInternalWidths();\r
+ }\r
+ }\r
+\r
+ private void setRelativeInternalWidths() {\r
+ DOM.setStyleAttribute(getElement(), "position", "relative");\r
+ buttons.setWidth("16%");\r
+ options.setWidth("42%");\r
+ selections.setWidth("42%");\r
+ widthSet = true;\r
+ }\r
+}\r
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client.ui;
+
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.Tree;
+import com.google.gwt.user.client.ui.VerticalPanel;
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;
+import com.itmill.toolkit.terminal.gwt.client.Paintable;
+import com.itmill.toolkit.terminal.gwt.client.UIDL;
+
+public class IUnknownComponent extends Composite implements Paintable {
+
+ com.google.gwt.user.client.ui.Label caption = new com.google.gwt.user.client.ui.Label();;
+ Tree uidlTree = new Tree();
+
+ public IUnknownComponent() {
+ final VerticalPanel panel = new VerticalPanel();
+ panel.add(caption);
+ panel.add(uidlTree);
+ initWidget(panel);
+ setStyleName("itmill-unknown");
+ caption.setStyleName("itmill-unknown-caption");
+ }
+
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+ if (client.updateComponent(this, uidl, false)) {
+ return;
+ }
+ setCaption("Client faced an unknown component type. Unrendered UIDL:");
+ uidlTree.clear();
+ uidlTree.addItem(uidl.dir());
+ }
+
+ public void setCaption(String c) {
+ caption.setText(c);
+ }
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client.ui;
+
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.ClickListener;
+import com.google.gwt.user.client.ui.FileUpload;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.FormHandler;
+import com.google.gwt.user.client.ui.FormPanel;
+import com.google.gwt.user.client.ui.FormSubmitCompleteEvent;
+import com.google.gwt.user.client.ui.FormSubmitEvent;
+import com.google.gwt.user.client.ui.Panel;
+import com.google.gwt.user.client.ui.Widget;
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;
+import com.itmill.toolkit.terminal.gwt.client.Paintable;
+import com.itmill.toolkit.terminal.gwt.client.UIDL;
+
+public class IUpload extends FormPanel implements Paintable, ClickListener,
+ FormHandler {
+
+ public static final String CLASSNAME = "i-upload";
+
+ /**
+ * FileUpload component that opens native OS dialog to select file.
+ */
+ FileUpload fu = new FileUpload();
+
+ Panel panel = new FlowPanel();
+
+ ApplicationConnection client;
+
+ private String paintableId;
+
+ /**
+ * Button that initiates uploading
+ */
+ private final Button submitButton;
+
+ /**
+ * When expecting big files, programmer may initiate some UI changes when
+ * uploading the file starts. Bit after submitting file we'll visit the
+ * server to check possible changes.
+ */
+ private Timer t;
+
+ /**
+ * some browsers tries to send form twice if submit is called in button
+ * click handler, some don't submit at all without it, so we need to track
+ * if form is already being submitted
+ */
+ private boolean submitted = false;
+
+ private boolean enabled = true;
+
+ public IUpload() {
+ super();
+ setEncoding(FormPanel.ENCODING_MULTIPART);
+ setMethod(FormPanel.METHOD_POST);
+
+ setWidget(panel);
+ panel.add(fu);
+ submitButton = new Button();
+ submitButton.addClickListener(this);
+ panel.add(submitButton);
+
+ addFormHandler(this);
+
+ setStyleName(CLASSNAME);
+ }
+
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+ if (client.updateComponent(this, uidl, true)) {
+ return;
+ }
+ this.client = client;
+ paintableId = uidl.getId();
+ setAction(client.getAppUri());
+ submitButton.setText(uidl.getStringAttribute("buttoncaption"));
+ fu.setName(paintableId + "_file");
+
+ if (uidl.hasAttribute("disabled") || uidl.hasAttribute("readonly")) {
+ disableUpload();
+ } else if (uidl.getBooleanAttribute("state")) {
+ enableUploaod();
+ }
+
+ }
+
+ public void onClick(Widget sender) {
+ submit();
+ }
+
+ public void onSubmit(FormSubmitEvent event) {
+ if (fu.getFilename().length() == 0 || submitted || !enabled) {
+ event.setCancelled(true);
+ ApplicationConnection
+ .getConsole()
+ .log(
+ "Submit cancelled (disabled, no file or already submitted)");
+ return;
+ }
+ // flush possibly pending variable changes, so they will be handled
+ // before upload
+ client.sendPendingVariableChanges();
+
+ submitted = true;
+ ApplicationConnection.getConsole().log("Submitted form");
+
+ disableUpload();
+
+ /*
+ * Visit server a moment after upload has started to see possible
+ * changes from UploadStarted event. Will be cleared on complete.
+ */
+ t = new Timer() {
+ public void run() {
+ client.sendPendingVariableChanges();
+ }
+ };
+ t.schedule(800);
+ }
+
+ protected void disableUpload() {
+ submitButton.setEnabled(false);
+ fu.setVisible(false);
+ enabled = false;
+ }
+
+ protected void enableUploaod() {
+ submitButton.setEnabled(true);
+ fu.setVisible(true);
+ enabled = true;
+ }
+
+ public void onSubmitComplete(FormSubmitCompleteEvent event) {
+ if (client != null) {
+ if (t != null) {
+ t.cancel();
+ }
+ ApplicationConnection.getConsole().log("Submit complete");
+ client.sendPendingVariableChanges();
+ }
+ submitted = false;
+ enableUploaod();
+ }
+
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client.ui;
+
+import java.util.HashSet;
+import java.util.Iterator;
+
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.WindowResizeListener;
+import com.google.gwt.user.client.ui.RootPanel;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;
+import com.itmill.toolkit.terminal.gwt.client.BrowserInfo;
+import com.itmill.toolkit.terminal.gwt.client.Paintable;
+import com.itmill.toolkit.terminal.gwt.client.UIDL;
+import com.itmill.toolkit.terminal.gwt.client.Util;
+
+/**
+ *
+ */
+public class IView extends SimplePanel implements Paintable,
+ WindowResizeListener {
+
+ private static final String CLASSNAME = "i-view";
+
+ private String theme;
+
+ private Paintable layout;
+
+ private final HashSet subWindows = new HashSet();
+
+ private String id;
+
+ private ShortcutActionHandler actionHandler;
+
+ /** stored width for IE resize optiomization */
+ private int width;
+
+ /** stored height for IE resize optiomization */
+ private int height;
+
+ /**
+ * We are postponing resize process with IE. IE bugs with scrollbars in some
+ * situations, that causes false onWindowResized calls. With Timer we will
+ * give IE some time to decide if it really wants to keep current size
+ * (scrollbars).
+ */
+ private Timer resizeTimer;
+
+ public IView(String elementId) {
+ super();
+ setStyleName(CLASSNAME);
+
+ DOM.sinkEvents(getElement(), Event.ONKEYDOWN);
+
+ DOM.setElementProperty(getElement(), "tabIndex", "0");
+
+ RootPanel.get(elementId).add(this);
+
+ Window.addWindowResizeListener(this);
+
+ // set focus to iview element by default to listen possible keyboard
+ // shortcuts
+ if (BrowserInfo.get().isOpera() || BrowserInfo.get().isSafari()
+ && BrowserInfo.get().getWebkitVersion() < 526) {
+ // old webkits don't support focusing div elements
+ Element fElem = DOM.createInputCheck();
+ DOM.setStyleAttribute(fElem, "margin", "0");
+ DOM.setStyleAttribute(fElem, "padding", "0");
+ DOM.setStyleAttribute(fElem, "border", "0");
+ DOM.setStyleAttribute(fElem, "outline", "0");
+ DOM.setStyleAttribute(fElem, "width", "1px");
+ DOM.setStyleAttribute(fElem, "height", "1px");
+ DOM.setStyleAttribute(fElem, "position", "absolute");
+ DOM.setStyleAttribute(fElem, "opacity", "0.1");
+ DOM.appendChild(getElement(), fElem);
+ focus(fElem);
+ } else {
+ focus(getElement());
+ }
+
+ }
+
+ private static native void focus(Element el)
+ /*-{
+ try {
+ el.focus();
+ } catch (e) {
+
+ }
+ }-*/;
+
+ public String getTheme() {
+ return theme;
+ }
+
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+
+ id = uidl.getId();
+
+ // Some attributes to note
+ theme = uidl.getStringAttribute("theme");
+ if (uidl.hasAttribute("style")) {
+ addStyleName(uidl.getStringAttribute("style"));
+ }
+
+ com.google.gwt.user.client.Window.setTitle(uidl
+ .getStringAttribute("caption"));
+
+ // Process children
+ int childIndex = 0;
+
+ // Open URL:s
+ while (childIndex < uidl.getChildCount()
+ && "open".equals(uidl.getChildUIDL(childIndex).getTag())) {
+ final UIDL open = uidl.getChildUIDL(childIndex);
+ final String url = open.getStringAttribute("src");
+ final String target = open.getStringAttribute("name");
+ if (target == null) {
+ goTo(url);
+ } else {
+ // TODO width & height
+ Window.open(url, target != null ? target : null, "");
+ }
+ childIndex++;
+ }
+
+ // Draw this application level window
+ UIDL childUidl = uidl.getChildUIDL(childIndex);
+ final Paintable lo = client.getPaintable(childUidl);
+
+ if (layout != null) {
+ if (layout != lo) {
+ // remove old
+ client.unregisterPaintable(layout);
+ // add new
+ setWidget((Widget) lo);
+ layout = lo;
+ }
+ } else {
+ setWidget((Widget) lo);
+ layout = lo;
+ }
+ layout.updateFromUIDL(childUidl, client);
+
+ // Update subwindows
+ final HashSet removedSubWindows = new HashSet(subWindows);
+
+ // Open new windows
+ while ((childUidl = uidl.getChildUIDL(childIndex++)) != null) {
+ if ("window".equals(childUidl.getTag())) {
+ final Paintable w = client.getPaintable(childUidl);
+ if (subWindows.contains(w)) {
+ removedSubWindows.remove(w);
+ } else {
+ subWindows.add(w);
+ }
+ w.updateFromUIDL(childUidl, client);
+ } else if ("actions".equals(childUidl.getTag())) {
+ if (actionHandler == null) {
+ actionHandler = new ShortcutActionHandler(id, client);
+ }
+ actionHandler.updateActionMap(childUidl);
+ } else if (childUidl.getTag().equals("notifications")) {
+ for (final Iterator it = childUidl.getChildIterator(); it
+ .hasNext();) {
+ final UIDL notification = (UIDL) it.next();
+ String html = "";
+ if (notification.hasAttribute("icon")) {
+ final String parsedUri = client
+ .translateToolkitUri(notification
+ .getStringAttribute("icon"));
+ html += "<IMG src=\"" + parsedUri + "\" />";
+ }
+ if (notification.hasAttribute("caption")) {
+ html += "<H1>"
+ + notification.getStringAttribute("caption")
+ + "</H1>";
+ }
+ if (notification.hasAttribute("message")) {
+ html += "<p>"
+ + notification.getStringAttribute("message")
+ + "</p>";
+ }
+
+ final String style = notification.hasAttribute("style") ? notification
+ .getStringAttribute("style")
+ : null;
+ final int position = notification
+ .getIntAttribute("position");
+ final int delay = notification.getIntAttribute("delay");
+ new Notification(delay).show(html, position, style);
+ }
+ }
+ }
+
+ // Close old windows
+ for (final Iterator rem = removedSubWindows.iterator(); rem.hasNext();) {
+ final IWindow w = (IWindow) rem.next();
+ client.unregisterPaintable(w);
+ subWindows.remove(w);
+ w.hide();
+ }
+
+ onWindowResized(Window.getClientWidth(), Window.getClientHeight());
+ // IE somehow fails some layout on first run, force layout
+ // functions
+ // Util.runDescendentsLayout(this);
+
+ }
+
+ public void onBrowserEvent(Event event) {
+ super.onBrowserEvent(event);
+ if (DOM.eventGetType(event) == Event.ONKEYDOWN && actionHandler != null) {
+ actionHandler.handleKeyboardEvent(event);
+ return;
+ }
+ }
+
+ public void onWindowResized(int width, int height) {
+ if (Util.isIE()) {
+ /*
+ * IE will give us some false resized events due bugs with
+ * scrollbars. Postponing layout phase to see if size was really
+ * changed.
+ */
+ if (resizeTimer == null) {
+ resizeTimer = new Timer() {
+ public void run() {
+ boolean changed = false;
+ if (IView.this.width != getOffsetWidth()) {
+ IView.this.width = getOffsetWidth();
+ changed = true;
+ ApplicationConnection.getConsole().log(
+ "window w" + IView.this.width);
+ }
+ if (IView.this.height != getOffsetHeight()) {
+ IView.this.height = getOffsetHeight();
+ changed = true;
+ ApplicationConnection.getConsole().log(
+ "window h" + IView.this.height);
+ }
+ if (changed) {
+ ApplicationConnection
+ .getConsole()
+ .log(
+ "Running layout functions due window resize");
+ Util.runDescendentsLayout(IView.this);
+ }
+ }
+ };
+ } else {
+ resizeTimer.cancel();
+ }
+ resizeTimer.schedule(200);
+ } else {
+ // temporary set overflow hidden, not to let scrollbars disturb
+ // layout functions
+ final String overflow = DOM.getStyleAttribute(getElement(),
+ "overflow");
+ DOM.setStyleAttribute(getElement(), "overflow", "hidden");
+ ApplicationConnection.getConsole().log(
+ "Running layout functions due window resize");
+ Util.runDescendentsLayout(this);
+ DOM.setStyleAttribute(getElement(), "overflow", overflow);
+ }
+ }
+
+ public native static void goTo(String url)
+ /*-{
+ $wnd.location = url;
+ }-*/;
+
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client.ui;
+
+import java.util.Iterator;
+import java.util.Vector;
+
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.DeferredCommand;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.Frame;
+import com.google.gwt.user.client.ui.PopupPanel;
+import com.google.gwt.user.client.ui.RootPanel;
+import com.google.gwt.user.client.ui.ScrollListener;
+import com.google.gwt.user.client.ui.ScrollPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;
+import com.itmill.toolkit.terminal.gwt.client.BrowserInfo;
+import com.itmill.toolkit.terminal.gwt.client.Paintable;
+import com.itmill.toolkit.terminal.gwt.client.UIDL;
+import com.itmill.toolkit.terminal.gwt.client.Util;
+
+/**
+ * "Sub window" component.
+ *
+ * TODO update position / scrollposition / size to client
+ *
+ * @author IT Mill Ltd
+ */
+public class IWindow extends PopupPanel implements Paintable, ScrollListener {
+
+ private static final int MIN_HEIGHT = 60;
+
+ private static final int MIN_WIDTH = 80;
+
+ private static Vector windowOrder = new Vector();
+
+ public static final String CLASSNAME = "i-window";
+
+ /** pixels used by inner borders and paddings horizontally */
+ protected static final int BORDER_WIDTH_HORIZONTAL = 41;
+
+ /** pixels used by headers, footers, inner borders and paddings vertically */
+ protected static final int BORDER_WIDTH_VERTICAL = 58;
+
+ private static final int STACKING_OFFSET_PIXELS = 15;
+
+ private static final int Z_INDEX_BASE = 10000;
+
+ private Paintable layout;
+
+ private Element contents;
+
+ private Element header;
+
+ private Element footer;
+
+ private Element resizeBox;
+
+ private final ScrollPanel contentPanel = new ScrollPanel();
+
+ private boolean dragging;
+
+ private int startX;
+
+ private int startY;
+
+ private int origX;
+
+ private int origY;
+
+ private boolean resizing;
+
+ private int origW;
+
+ private int origH;
+
+ private Element closeBox;
+
+ protected ApplicationConnection client;
+
+ private String id;
+
+ ShortcutActionHandler shortcutHandler;
+
+ /** Last known positionx read from UIDL or updated to application connection */
+ private int uidlPositionX = -1;
+
+ /** Last known positiony read from UIDL or updated to application connection */
+ private int uidlPositionY = -1;
+
+ private boolean modal = false;
+
+ private Element modalityCurtain;
+ private Element draggingCurtain;
+
+ private Element headerText;
+
+ public IWindow() {
+ super();
+ final int order = windowOrder.size();
+ setWindowOrder(order);
+ windowOrder.add(this);
+ constructDOM();
+ setPopupPosition(order * STACKING_OFFSET_PIXELS, order
+ * STACKING_OFFSET_PIXELS);
+ contentPanel.addScrollListener(this);
+ }
+
+ private void bringToFront() {
+ int curIndex = windowOrder.indexOf(this);
+ if (curIndex + 1 < windowOrder.size()) {
+ windowOrder.remove(this);
+ windowOrder.add(this);
+ for (; curIndex < windowOrder.size(); curIndex++) {
+ ((IWindow) windowOrder.get(curIndex)).setWindowOrder(curIndex);
+ }
+ }
+ }
+
+ /**
+ * Returns true if window is the topmost window
+ *
+ * @return
+ */
+ private boolean isActive() {
+ return windowOrder.lastElement().equals(this);
+ }
+
+ public void setWindowOrder(int order) {
+ int zIndex = (order + Z_INDEX_BASE);
+ if (modal) {
+ zIndex += 1000;
+ DOM.setStyleAttribute(modalityCurtain, "zIndex", "" + zIndex);
+ }
+ DOM.setStyleAttribute(getElement(), "zIndex", "" + zIndex);
+ }
+
+ protected void constructDOM() {
+ header = DOM.createDiv();
+ DOM.setElementProperty(header, "className", CLASSNAME + "-outerheader");
+ headerText = DOM.createDiv();
+ DOM.setElementProperty(headerText, "className", CLASSNAME + "-header");
+ contents = DOM.createDiv();
+ DOM.setElementProperty(contents, "className", CLASSNAME + "-contents");
+ footer = DOM.createDiv();
+ DOM.setElementProperty(footer, "className", CLASSNAME + "-footer");
+ resizeBox = DOM.createDiv();
+ DOM
+ .setElementProperty(resizeBox, "className", CLASSNAME
+ + "-resizebox");
+ closeBox = DOM.createDiv();
+ DOM.setElementProperty(closeBox, "className", CLASSNAME + "-closebox");
+ DOM.appendChild(footer, resizeBox);
+
+ DOM.sinkEvents(getElement(), Event.ONLOSECAPTURE);
+ DOM.sinkEvents(closeBox, Event.ONCLICK);
+ DOM.sinkEvents(contents, Event.ONCLICK);
+
+ final Element wrapper = DOM.createDiv();
+ DOM.setElementProperty(wrapper, "className", CLASSNAME + "-wrap");
+
+ final Element wrapper2 = DOM.createDiv();
+ DOM.setElementProperty(wrapper2, "className", CLASSNAME + "-wrap2");
+
+ DOM.sinkEvents(wrapper, Event.ONKEYDOWN);
+
+ DOM.appendChild(wrapper2, closeBox);
+ DOM.appendChild(wrapper2, header);
+ DOM.appendChild(header, headerText);
+ DOM.appendChild(wrapper2, contents);
+ DOM.appendChild(wrapper2, footer);
+ DOM.appendChild(wrapper, wrapper2);
+ DOM.appendChild(super.getContainerElement(), wrapper);
+ DOM.setElementProperty(getElement(), "className", CLASSNAME);
+
+ sinkEvents(Event.MOUSEEVENTS);
+
+ setWidget(contentPanel);
+ }
+
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+ id = uidl.getId();
+ this.client = client;
+
+ // Workaround needed for Testing Tools (GWT generates window DOM
+ // slightly different in different browsers).
+ DOM.setElementProperty(closeBox, "id", id + "_window_close");
+
+ if (uidl.hasAttribute("invisible")) {
+ this.hide();
+ return;
+ }
+
+ if (client.updateComponent(this, uidl, false)) {
+ return;
+ }
+
+ if (uidl.getBooleanAttribute("modal") != modal) {
+ setModal(!modal);
+ }
+
+ // Initialize the size from UIDL
+ // FIXME relational size is for outer size, others are applied for
+ // content
+ if (uidl.hasVariable("width")) {
+ final String width = uidl.getStringVariable("width");
+ if (width.indexOf("px") < 0) {
+ DOM.setStyleAttribute(getElement(), "width", width);
+ } else {
+ setWidth(width);
+ }
+ }
+
+ // Initialize the position form UIDL
+ try {
+ final int positionx = uidl.getIntVariable("positionx");
+ final int positiony = uidl.getIntVariable("positiony");
+ if (positionx >= 0 && positiony >= 0) {
+ setPopupPosition(positionx, positiony);
+ }
+ } catch (final IllegalArgumentException e) {
+ // Silently ignored as positionx and positiony are not required
+ // parameters
+ }
+
+ if (!isAttached()) {
+ show();
+ }
+
+ // Height set after show so we can detect space used by decorations
+ if (uidl.hasVariable("height")) {
+ final String height = uidl.getStringVariable("height");
+ if (height.indexOf("%") > 0) {
+ int winHeight = Window.getClientHeight();
+ float percent = Float.parseFloat(height.substring(0, height
+ .indexOf("%"))) / 100.0f;
+ int contentPixels = (int) (winHeight * percent);
+ contentPixels -= (DOM.getElementPropertyInt(getElement(),
+ "offsetHeight") - DOM.getElementPropertyInt(contents,
+ "offsetHeight"));
+ // FIXME hardcoded contents elements border size
+ contentPixels -= 2;
+
+ setHeight(contentPixels + "px");
+ } else {
+ setHeight(height);
+ }
+ }
+
+ if (uidl.hasAttribute("caption")) {
+ setCaption(uidl.getStringAttribute("caption"), uidl
+ .getStringAttribute("icon"));
+ }
+
+ boolean showingUrl = false;
+ int childIndex = 0;
+ UIDL childUidl = uidl.getChildUIDL(childIndex++);
+ while ("open".equals(childUidl.getTag())) {
+ // TODO multiple opens with the same target will in practice just
+ // open the last one - should we fix that somehow?
+ final String parsedUri = client.translateToolkitUri(childUidl
+ .getStringAttribute("src"));
+ if (!childUidl.hasAttribute("name")) {
+ final Frame frame = new Frame();
+ DOM.setStyleAttribute(frame.getElement(), "width", "100%");
+ DOM.setStyleAttribute(frame.getElement(), "height", "100%");
+ DOM.setStyleAttribute(frame.getElement(), "border", "0px");
+ frame.setUrl(parsedUri);
+ contentPanel.setWidget(frame);
+ showingUrl = true;
+ } else {
+ final String target = childUidl.getStringAttribute("name");
+ Window.open(parsedUri, target, "");
+ }
+ childUidl = uidl.getChildUIDL(childIndex++);
+ }
+
+ final Paintable lo = client.getPaintable(childUidl);
+ if (layout != null) {
+ if (layout != lo) {
+ // remove old
+ client.unregisterPaintable(layout);
+ contentPanel.remove((Widget) layout);
+ // add new
+ if (!showingUrl) {
+ contentPanel.setWidget((Widget) lo);
+ }
+ layout = lo;
+ }
+ } else if (!showingUrl) {
+ contentPanel.setWidget((Widget) lo);
+ }
+ lo.updateFromUIDL(childUidl, client);
+
+ // we may have actions and notifications
+ if (uidl.getChildCount() > 1) {
+ final int cnt = uidl.getChildCount();
+ for (int i = 1; i < cnt; i++) {
+ childUidl = uidl.getChildUIDL(i);
+ if (childUidl.getTag().equals("actions")) {
+ if (shortcutHandler == null) {
+ shortcutHandler = new ShortcutActionHandler(id, client);
+ }
+ shortcutHandler.updateActionMap(childUidl);
+ } else if (childUidl.getTag().equals("notifications")) {
+ // TODO needed? move ->
+ for (final Iterator it = childUidl.getChildIterator(); it
+ .hasNext();) {
+ final UIDL notification = (UIDL) it.next();
+ String html = "";
+ if (notification.hasAttribute("icon")) {
+ final String parsedUri = client
+ .translateToolkitUri(notification
+ .getStringAttribute("icon"));
+ html += "<img src=\"" + parsedUri + "\" />";
+ }
+ if (notification.hasAttribute("caption")) {
+ html += "<h1>"
+ + notification
+ .getStringAttribute("caption")
+ + "</h1>";
+ }
+ if (notification.hasAttribute("message")) {
+ html += "<p>"
+ + notification
+ .getStringAttribute("message")
+ + "</p>";
+ }
+
+ final String style = notification.hasAttribute("style") ? notification
+ .getStringAttribute("style")
+ : null;
+ final int position = notification
+ .getIntAttribute("position");
+ final int delay = notification.getIntAttribute("delay");
+ new Notification(delay).show(html, position, style);
+ }
+ }
+ }
+
+ }
+
+ // setting scrollposition must happen after children is rendered
+ contentPanel.setScrollPosition(uidl.getIntVariable("scrolltop"));
+ contentPanel.setHorizontalScrollPosition(uidl
+ .getIntVariable("scrollleft"));
+
+ }
+
+ public void show() {
+ if (modal) {
+ showModalityCurtain();
+ }
+ super.show();
+
+ setFF2CaretFixEnabled(true);
+ fixFF3OverflowBug();
+ }
+
+ /** Disable overflow auto with FF3 to fix #1837. */
+ private void fixFF3OverflowBug() {
+ if (BrowserInfo.get().isFF3()) {
+ DeferredCommand.addCommand(new Command() {
+ public void execute() {
+ DOM.setStyleAttribute(getElement(), "overflow", "");
+ }
+ });
+ }
+ }
+
+ /**
+ * Fix "missing cursor" browser bug workaround for FF2 in Windows and Linux.
+ *
+ * Calling this method has no effect on other browsers than the ones based
+ * on Gecko 1.8
+ *
+ * @param enable
+ */
+ private void setFF2CaretFixEnabled(boolean enable) {
+ if (BrowserInfo.get().isFF2()) {
+ if (enable) {
+ DeferredCommand.addCommand(new Command() {
+ public void execute() {
+ DOM.setStyleAttribute(getElement(), "overflow", "auto");
+ }
+ });
+ } else {
+ DOM.setStyleAttribute(getElement(), "overflow", "");
+ }
+ }
+ }
+
+ public void hide() {
+ if (modal) {
+ hideModalityCurtain();
+ }
+ super.hide();
+ }
+
+ private void setModal(boolean modality) {
+ modal = modality;
+ if (modal) {
+ modalityCurtain = DOM.createDiv();
+ DOM.setElementProperty(modalityCurtain, "className", CLASSNAME
+ + "-modalitycurtain");
+ if (isAttached()) {
+ showModalityCurtain();
+ bringToFront();
+ } else {
+ DeferredCommand.addCommand(new Command() {
+ public void execute() {
+ // modal window must on top of others
+ bringToFront();
+ }
+ });
+ }
+ } else {
+ if (modalityCurtain != null) {
+ if (isAttached()) {
+ hideModalityCurtain();
+ }
+ modalityCurtain = null;
+ }
+ }
+ }
+
+ private void showModalityCurtain() {
+ if (Util.isFF2()) {
+ DOM.setStyleAttribute(modalityCurtain, "height", DOM
+ .getElementPropertyInt(RootPanel.getBodyElement(),
+ "offsetHeight")
+ + "px");
+ DOM.setStyleAttribute(modalityCurtain, "position", "absolute");
+ }
+ DOM.appendChild(RootPanel.getBodyElement(), modalityCurtain);
+ }
+
+ private void hideModalityCurtain() {
+ DOM.removeChild(RootPanel.getBodyElement(), modalityCurtain);
+ }
+
+ /*
+ * Shows (or hides) an empty div on top of all other content; used when
+ * resizing or moving, so that iframes (etc) do not steal event.
+ */
+ private void showDraggingCurtain(boolean show) {
+ if (show && draggingCurtain == null) {
+
+ setFF2CaretFixEnabled(false); // makes FF2 slow
+
+ draggingCurtain = DOM.createDiv();
+ DOM.setStyleAttribute(draggingCurtain, "position", "absolute");
+ DOM.setStyleAttribute(draggingCurtain, "top", "0px");
+ DOM.setStyleAttribute(draggingCurtain, "left", "0px");
+ DOM.setStyleAttribute(draggingCurtain, "width", "100%");
+ DOM.setStyleAttribute(draggingCurtain, "height", "100%");
+ DOM.setStyleAttribute(draggingCurtain, "zIndex", ""
+ + ToolkitOverlay.Z_INDEX);
+
+ DOM.appendChild(RootPanel.getBodyElement(), draggingCurtain);
+ } else if (!show && draggingCurtain != null) {
+
+ setFF2CaretFixEnabled(true); // makes FF2 slow
+
+ DOM.removeChild(RootPanel.getBodyElement(), draggingCurtain);
+ draggingCurtain = null;
+ }
+
+ }
+
+ public void setPopupPosition(int left, int top) {
+ super.setPopupPosition(left, top);
+ if (left != uidlPositionX && client != null) {
+ client.updateVariable(id, "positionx", left, false);
+ uidlPositionX = left;
+ }
+ if (top != uidlPositionY && client != null) {
+ client.updateVariable(id, "positiony", top, false);
+ uidlPositionY = top;
+ }
+ }
+
+ public void setCaption(String c) {
+ setCaption(c, null);
+ }
+
+ public void setCaption(String c, String icon) {
+ String html = c;
+ if (icon != null) {
+ icon = client.translateToolkitUri(icon);
+ html = "<img src=\"" + icon + "\" class=\"i-icon\" />" + html;
+ }
+ DOM.setInnerHTML(headerText, html);
+ }
+
+ protected Element getContainerElement() {
+ return contents;
+ }
+
+ public void onBrowserEvent(final Event event) {
+ final int type = DOM.eventGetType(event);
+
+ if (type == Event.ONKEYDOWN && shortcutHandler != null) {
+ shortcutHandler.handleKeyboardEvent(event);
+ return;
+ }
+
+ final Element target = DOM.eventGetTarget(event);
+
+ if (client != null && !DOM.compare(target, header)) {
+ client.handleTooltipEvent(event, this);
+ }
+
+ if (resizing || DOM.compare(resizeBox, target)) {
+ onResizeEvent(event);
+ DOM.eventCancelBubble(event, true);
+ } else if (DOM.compare(target, closeBox)) {
+ if (type == Event.ONCLICK) {
+ onCloseClick();
+ DOM.eventCancelBubble(event, true);
+ }
+ } else if (dragging || !DOM.isOrHasChild(contents, target)) {
+ onDragEvent(event);
+ DOM.eventCancelBubble(event, true);
+ } else if (type == Event.ONCLICK) {
+ // clicked inside window, ensure to be on top
+ if (!isActive()) {
+ bringToFront();
+ }
+ }
+ }
+
+ private void onCloseClick() {
+ client.updateVariable(id, "close", true, true);
+ }
+
+ private void onResizeEvent(Event event) {
+ switch (DOM.eventGetType(event)) {
+ case Event.ONMOUSEDOWN:
+ if (!isActive()) {
+ bringToFront();
+ }
+ showDraggingCurtain(true);
+ resizing = true;
+ startX = DOM.eventGetScreenX(event);
+ startY = DOM.eventGetScreenY(event);
+ origW = getWidget().getOffsetWidth();
+ origH = getWidget().getOffsetHeight();
+ DOM.setCapture(getElement());
+ DOM.eventPreventDefault(event);
+ break;
+ case Event.ONMOUSEUP:
+ showDraggingCurtain(false);
+ resizing = false;
+ DOM.releaseCapture(getElement());
+ setSize(event, true);
+ break;
+ case Event.ONLOSECAPTURE:
+ showDraggingCurtain(false);
+ resizing = false;
+ case Event.ONMOUSEMOVE:
+ if (resizing) {
+ setSize(event, false);
+ DOM.eventPreventDefault(event);
+ }
+ break;
+ default:
+ DOM.eventPreventDefault(event);
+ break;
+ }
+ }
+
+ public void setSize(Event event, boolean updateVariables) {
+ int w = DOM.eventGetScreenX(event) - startX + origW;
+ if (w < MIN_WIDTH) {
+ w = MIN_WIDTH;
+ }
+ int h = DOM.eventGetScreenY(event) - startY + origH;
+ if (h < MIN_HEIGHT) {
+ h = MIN_HEIGHT;
+ }
+ setWidth(w + "px");
+ setHeight(h + "px");
+ if (updateVariables) {
+ // sending width back always as pixels, no need for unit
+ client.updateVariable(id, "width", w, false);
+ client.updateVariable(id, "height", h, false);
+ }
+ // Update child widget dimensions
+ Util.runDescendentsLayout(this);
+ }
+
+ public void setWidth(String width) {
+ if (!"".equals(width)) {
+ DOM
+ .setStyleAttribute(
+ getElement(),
+ "width",
+ (Integer.parseInt(width.substring(0,
+ width.length() - 2)) + BORDER_WIDTH_HORIZONTAL)
+ + "px");
+ }
+ }
+
+ private void onDragEvent(Event event) {
+ switch (DOM.eventGetType(event)) {
+ case Event.ONMOUSEDOWN:
+ if (!isActive()) {
+ bringToFront();
+ }
+ showDraggingCurtain(true);
+ dragging = true;
+ startX = DOM.eventGetScreenX(event);
+ startY = DOM.eventGetScreenY(event);
+ origX = DOM.getAbsoluteLeft(getElement());
+ origY = DOM.getAbsoluteTop(getElement());
+ DOM.setCapture(getElement());
+ DOM.eventPreventDefault(event);
+ break;
+ case Event.ONMOUSEUP:
+ dragging = false;
+ showDraggingCurtain(false);
+ DOM.releaseCapture(getElement());
+ break;
+ case Event.ONLOSECAPTURE:
+ showDraggingCurtain(false);
+ dragging = false;
+ break;
+ case Event.ONMOUSEMOVE:
+ if (dragging) {
+ final int x = DOM.eventGetScreenX(event) - startX + origX;
+ final int y = DOM.eventGetScreenY(event) - startY + origY;
+ setPopupPosition(x, y);
+ DOM.eventPreventDefault(event);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ public boolean onEventPreview(Event event) {
+ if (dragging) {
+ onDragEvent(event);
+ return false;
+ } else if (resizing) {
+ onResizeEvent(event);
+ return false;
+ } else if (modal) {
+ // return false when modal and outside window
+ final Element target = DOM.eventGetTarget(event);
+ if (!DOM.isOrHasChild(getElement(), target)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public void onScroll(Widget widget, int scrollLeft, int scrollTop) {
+ client.updateVariable(id, "scrolltop", scrollTop, false);
+ client.updateVariable(id, "scrollleft", scrollLeft, false);
+ }
+
+ public void addStyleDependentName(String styleSuffix) {
+ // IWindow's getStyleElement() does not return the same element as
+ // getElement(), so we need to override this.
+ setStyleName(getElement(), getStylePrimaryName() + "-" + styleSuffix,
+ true);
+ }
+
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client.ui;
+
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.ui.UIObject;
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;
+
+public class Icon extends UIObject {
+ private final ApplicationConnection client;
+ private String myUri;
+
+ public Icon(ApplicationConnection client) {
+ setElement(DOM.createImg());
+ DOM.setElementProperty(getElement(), "alt", "icon");
+ setStyleName("i-icon");
+ this.client = client;
+ client.addPngFix(getElement());
+ }
+
+ public Icon(ApplicationConnection client, String uidlUri) {
+ this(client);
+ setUri(uidlUri);
+ }
+
+ public void setUri(String uidlUri) {
+ if (!uidlUri.equals(myUri)) {
+ String uri = client.translateToolkitUri(uidlUri);
+ DOM.setElementProperty(getElement(), "src", uri);
+ myUri = uidlUri;
+ }
+ }
+
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client.ui;
+
+public class MarginInfo {
+
+ private static final int TOP = 1;
+ private static final int RIGHT = 2;
+ private static final int BOTTOM = 4;
+ private static final int LEFT = 8;
+
+ private int bitMask;
+
+ public MarginInfo(int bitMask) {
+ this.bitMask = bitMask;
+ }
+
+ public MarginInfo(boolean top, boolean right, boolean bottom, boolean left) {
+ setMargins(top, right, bottom, left);
+ }
+
+ public void setMargins(boolean top, boolean right, boolean bottom,
+ boolean left) {
+ bitMask = top ? TOP : 0;
+ bitMask += right ? RIGHT : 0;
+ bitMask += bottom ? BOTTOM : 0;
+ bitMask += left ? LEFT : 0;
+ }
+
+ public boolean hasLeft() {
+ return (bitMask & LEFT) == LEFT;
+ }
+
+ public boolean hasRight() {
+ return (bitMask & RIGHT) == RIGHT;
+ }
+
+ public boolean hasTop() {
+ return (bitMask & TOP) == TOP;
+ }
+
+ public boolean hasBottom() {
+ return (bitMask & BOTTOM) == BOTTOM;
+ }
+
+ public int getBitMask() {
+ return bitMask;
+ }
+
+ public void setMargins(boolean enabled) {
+ if (enabled) {
+ bitMask = TOP + RIGHT + BOTTOM + LEFT;
+ } else {
+ bitMask = 0;
+ }
+ }
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client.ui;
+
+/*
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+// COPIED HERE DUE package privates in GWT
+import java.util.ArrayList;
+import java.util.List;
+
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.DeferredCommand;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.PopupListener;
+import com.google.gwt.user.client.ui.PopupPanel;
+import com.google.gwt.user.client.ui.Widget;
+
+/**
+ * A standard menu bar widget. A menu bar can contain any number of menu items,
+ * each of which can either fire a {@link com.google.gwt.user.client.Command} or
+ * open a cascaded menu bar.
+ *
+ * <p>
+ * <img class='gallery' src='MenuBar.png'/>
+ * </p>
+ *
+ * <h3>CSS Style Rules</h3>
+ * <ul class='css'>
+ * <li>.gwt-MenuBar { the menu bar itself }</li>
+ * <li>.gwt-MenuBar .gwt-MenuItem { menu items }</li>
+ * <li>.gwt-MenuBar .gwt-MenuItem-selected { selected menu items }</li>
+ * </ul>
+ *
+ * <p>
+ * <h3>Example</h3>
+ * {@example com.google.gwt.examples.MenuBarExample}
+ * </p>
+ */
+public class MenuBar extends Widget implements PopupListener {
+
+ private final Element body;
+ private final ArrayList items = new ArrayList();
+ private MenuBar parentMenu;
+ private PopupPanel popup;
+ private MenuItem selectedItem;
+ private MenuBar shownChildMenu;
+ private final boolean vertical;
+ private boolean autoOpen;
+
+ /**
+ * Creates an empty horizontal menu bar.
+ */
+ public MenuBar() {
+ this(false);
+ }
+
+ /**
+ * Creates an empty menu bar.
+ *
+ * @param vertical
+ * <code>true</code> to orient the menu bar vertically
+ */
+ public MenuBar(boolean vertical) {
+ super();
+
+ final Element table = DOM.createTable();
+ body = DOM.createTBody();
+ DOM.appendChild(table, body);
+
+ if (!vertical) {
+ final Element tr = DOM.createTR();
+ DOM.appendChild(body, tr);
+ }
+
+ this.vertical = vertical;
+
+ final Element outer = DOM.createDiv();
+ DOM.appendChild(outer, table);
+ setElement(outer);
+
+ sinkEvents(Event.ONCLICK | Event.ONMOUSEOVER | Event.ONMOUSEOUT);
+ setStyleName("gwt-MenuBar");
+ }
+
+ /**
+ * Adds a menu item to the bar.
+ *
+ * @param item
+ * the item to be added
+ */
+ public void addItem(MenuItem item) {
+ Element tr;
+ if (vertical) {
+ tr = DOM.createTR();
+ DOM.appendChild(body, tr);
+ } else {
+ tr = DOM.getChild(body, 0);
+ }
+
+ DOM.appendChild(tr, item.getElement());
+
+ item.setParentMenu(this);
+ item.setSelectionStyle(false);
+ items.add(item);
+ }
+
+ /**
+ * Adds a menu item to the bar, that will fire the given command when it is
+ * selected.
+ *
+ * @param text
+ * the item's text
+ * @param asHTML
+ * <code>true</code> to treat the specified text as html
+ * @param cmd
+ * the command to be fired
+ * @return the {@link MenuItem} object created
+ */
+ public MenuItem addItem(String text, boolean asHTML, Command cmd) {
+ final MenuItem item = new MenuItem(text, asHTML, cmd);
+ addItem(item);
+ return item;
+ }
+
+ /**
+ * Adds a menu item to the bar, that will open the specified menu when it is
+ * selected.
+ *
+ * @param text
+ * the item's text
+ * @param asHTML
+ * <code>true</code> to treat the specified text as html
+ * @param popup
+ * the menu to be cascaded from it
+ * @return the {@link MenuItem} object created
+ */
+ public MenuItem addItem(String text, boolean asHTML, MenuBar popup) {
+ final MenuItem item = new MenuItem(text, asHTML, popup);
+ addItem(item);
+ return item;
+ }
+
+ /**
+ * Adds a menu item to the bar, that will fire the given command when it is
+ * selected.
+ *
+ * @param text
+ * the item's text
+ * @param cmd
+ * the command to be fired
+ * @return the {@link MenuItem} object created
+ */
+ public MenuItem addItem(String text, Command cmd) {
+ final MenuItem item = new MenuItem(text, cmd);
+ addItem(item);
+ return item;
+ }
+
+ /**
+ * Adds a menu item to the bar, that will open the specified menu when it is
+ * selected.
+ *
+ * @param text
+ * the item's text
+ * @param popup
+ * the menu to be cascaded from it
+ * @return the {@link MenuItem} object created
+ */
+ public MenuItem addItem(String text, MenuBar popup) {
+ final MenuItem item = new MenuItem(text, popup);
+ addItem(item);
+ return item;
+ }
+
+ /**
+ * Removes all menu items from this menu bar.
+ */
+ public void clearItems() {
+ final Element container = getItemContainerElement();
+ while (DOM.getChildCount(container) > 0) {
+ DOM.removeChild(container, DOM.getChild(container, 0));
+ }
+ items.clear();
+ }
+
+ /**
+ * Gets whether this menu bar's child menus will open when the mouse is
+ * moved over it.
+ *
+ * @return <code>true</code> if child menus will auto-open
+ */
+ public boolean getAutoOpen() {
+ return autoOpen;
+ }
+
+ public void onBrowserEvent(Event event) {
+ super.onBrowserEvent(event);
+
+ final MenuItem item = findItem(DOM.eventGetTarget(event));
+ switch (DOM.eventGetType(event)) {
+ case Event.ONCLICK: {
+ // Fire an item's command when the user clicks on it.
+ if (item != null) {
+ doItemAction(item, true);
+ }
+ break;
+ }
+
+ case Event.ONMOUSEOVER: {
+ if (item != null) {
+ itemOver(item);
+ }
+ break;
+ }
+
+ case Event.ONMOUSEOUT: {
+ if (item != null) {
+ itemOver(null);
+ }
+ break;
+ }
+ }
+ }
+
+ public void onPopupClosed(PopupPanel sender, boolean autoClosed) {
+ // If the menu popup was auto-closed, close all of its parents as well.
+ if (autoClosed) {
+ closeAllParents();
+ }
+
+ // When the menu popup closes, remember that no item is
+ // currently showing a popup menu.
+ onHide();
+ shownChildMenu = null;
+ popup = null;
+ }
+
+ /**
+ * Removes the specified menu item from the bar.
+ *
+ * @param item
+ * the item to be removed
+ */
+ public void removeItem(MenuItem item) {
+ final int idx = items.indexOf(item);
+ if (idx == -1) {
+ return;
+ }
+
+ final Element container = getItemContainerElement();
+ DOM.removeChild(container, DOM.getChild(container, idx));
+ items.remove(idx);
+ }
+
+ /**
+ * Sets whether this menu bar's child menus will open when the mouse is
+ * moved over it.
+ *
+ * @param autoOpen
+ * <code>true</code> to cause child menus to auto-open
+ */
+ public void setAutoOpen(boolean autoOpen) {
+ this.autoOpen = autoOpen;
+ }
+
+ /**
+ * Returns a list containing the <code>MenuItem</code> objects in the menu
+ * bar. If there are no items in the menu bar, then an empty
+ * <code>List</code> object will be returned.
+ *
+ * @return a list containing the <code>MenuItem</code> objects in the menu
+ * bar
+ */
+ protected List getItems() {
+ return items;
+ }
+
+ /**
+ * Returns the <code>MenuItem</code> that is currently selected
+ * (highlighted) by the user. If none of the items in the menu are currently
+ * selected, then <code>null</code> will be returned.
+ *
+ * @return the <code>MenuItem</code> that is currently selected, or
+ * <code>null</code> if no items are currently selected
+ */
+ protected MenuItem getSelectedItem() {
+ return selectedItem;
+ }
+
+ protected void onDetach() {
+ // When the menu is detached, make sure to close all of its children.
+ if (popup != null) {
+ popup.hide();
+ }
+
+ super.onDetach();
+ }
+
+ /*
+ * Closes all parent menu popups.
+ */
+ void closeAllParents() {
+ MenuBar curMenu = this;
+ while (curMenu != null) {
+ curMenu.close();
+
+ if ((curMenu.parentMenu == null) && (curMenu.selectedItem != null)) {
+ curMenu.selectedItem.setSelectionStyle(false);
+ curMenu.selectedItem = null;
+ }
+
+ curMenu = curMenu.parentMenu;
+ }
+ }
+
+ /*
+ * Performs the action associated with the given menu item. If the item has
+ * a popup associated with it, the popup will be shown. If it has a command
+ * associated with it, and 'fireCommand' is true, then the command will be
+ * fired. Popups associated with other items will be hidden.
+ *
+ * @param item the item whose popup is to be shown. @param fireCommand
+ * <code>true</code> if the item's command should be fired, <code>false</code>
+ * otherwise.
+ */
+ void doItemAction(final MenuItem item, boolean fireCommand) {
+ // If the given item is already showing its menu, we're done.
+ if ((shownChildMenu != null) && (item.getSubMenu() == shownChildMenu)) {
+ return;
+ }
+
+ // If another item is showing its menu, then hide it.
+ if (shownChildMenu != null) {
+ shownChildMenu.onHide();
+ popup.hide();
+ }
+
+ // If the item has no popup, optionally fire its command.
+ if (item.getSubMenu() == null) {
+ if (fireCommand) {
+ // Close this menu and all of its parents.
+ closeAllParents();
+
+ // Fire the item's command.
+ final Command cmd = item.getCommand();
+ if (cmd != null) {
+ DeferredCommand.addCommand(cmd);
+ }
+ }
+ return;
+ }
+
+ // Ensure that the item is selected.
+ selectItem(item);
+
+ // Create a new popup for this item, and position it next to
+ // the item (below if this is a horizontal menu bar, to the
+ // right if it's a vertical bar).
+ popup = new ToolkitOverlay(true) {
+ {
+ setWidget(item.getSubMenu());
+ item.getSubMenu().onShow();
+ }
+
+ public boolean onEventPreview(Event event) {
+ // Hook the popup panel's event preview. We use this to keep it
+ // from
+ // auto-hiding when the parent menu is clicked.
+ switch (DOM.eventGetType(event)) {
+ case Event.ONCLICK:
+ // If the event target is part of the parent menu, suppress
+ // the
+ // event altogether.
+ final Element target = DOM.eventGetTarget(event);
+ final Element parentMenuElement = item.getParentMenu()
+ .getElement();
+ if (DOM.isOrHasChild(parentMenuElement, target)) {
+ return false;
+ }
+ break;
+ }
+
+ return super.onEventPreview(event);
+ }
+ };
+ popup.addPopupListener(this);
+
+ if (vertical) {
+ popup.setPopupPosition(item.getAbsoluteLeft()
+ + item.getOffsetWidth(), item.getAbsoluteTop());
+ } else {
+ popup.setPopupPosition(item.getAbsoluteLeft(), item
+ .getAbsoluteTop()
+ + item.getOffsetHeight());
+ }
+
+ shownChildMenu = item.getSubMenu();
+ item.getSubMenu().parentMenu = this;
+
+ // Show the popup, ensuring that the menubar's event preview remains on
+ // top
+ // of the popup's.
+ popup.show();
+ }
+
+ void itemOver(MenuItem item) {
+ if (item == null) {
+ // Don't clear selection if the currently selected item's menu is
+ // showing.
+ if ((selectedItem != null)
+ && (shownChildMenu == selectedItem.getSubMenu())) {
+ return;
+ }
+ }
+
+ // Style the item selected when the mouse enters.
+ selectItem(item);
+
+ // If child menus are being shown, or this menu is itself
+ // a child menu, automatically show an item's child menu
+ // when the mouse enters.
+ if (item != null) {
+ if ((shownChildMenu != null) || (parentMenu != null) || autoOpen) {
+ doItemAction(item, false);
+ }
+ }
+ }
+
+ void selectItem(MenuItem item) {
+ if (item == selectedItem) {
+ return;
+ }
+
+ if (selectedItem != null) {
+ selectedItem.setSelectionStyle(false);
+ }
+
+ if (item != null) {
+ item.setSelectionStyle(true);
+ }
+
+ selectedItem = item;
+ }
+
+ /**
+ * Closes this menu (if it is a popup).
+ */
+ private void close() {
+ if (parentMenu != null) {
+ parentMenu.popup.hide();
+ }
+ }
+
+ private MenuItem findItem(Element hItem) {
+ for (int i = 0; i < items.size(); ++i) {
+ final MenuItem item = (MenuItem) items.get(i);
+ if (DOM.isOrHasChild(item.getElement(), hItem)) {
+ return item;
+ }
+ }
+
+ return null;
+ }
+
+ private Element getItemContainerElement() {
+ if (vertical) {
+ return body;
+ } else {
+ return DOM.getChild(body, 0);
+ }
+ }
+
+ /*
+ * This method is called when a menu bar is hidden, so that it can hide any
+ * child popups that are currently being shown.
+ */
+ private void onHide() {
+ if (shownChildMenu != null) {
+ shownChildMenu.onHide();
+ popup.hide();
+ }
+ }
+
+ /*
+ * This method is called when a menu bar is shown.
+ */
+ private void onShow() {
+ // Select the first item when a menu is shown.
+ if (items.size() > 0) {
+ selectItem((MenuItem) items.get(0));
+ }
+ }
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client.ui;
+
+/*
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+// COPIED HERE DUE package privates in GWT
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.ui.HasHTML;
+import com.google.gwt.user.client.ui.UIObject;
+
+/**
+ * A widget that can be placed in a
+ * {@link com.google.gwt.user.client.ui.MenuBar}. Menu items can either fire a
+ * {@link com.google.gwt.user.client.Command} when they are clicked, or open a
+ * cascading sub-menu.
+ */
+public class MenuItem extends UIObject implements HasHTML {
+
+ private static final String DEPENDENT_STYLENAME_SELECTED_ITEM = "selected";
+
+ private Command command;
+ private MenuBar parentMenu, subMenu;
+
+ /**
+ * Constructs a new menu item that fires a command when it is selected.
+ *
+ * @param text
+ * the item's text
+ * @param cmd
+ * the command to be fired when it is selected
+ */
+ public MenuItem(String text, Command cmd) {
+ this(text, false);
+ setCommand(cmd);
+ }
+
+ /**
+ * Constructs a new menu item that fires a command when it is selected.
+ *
+ * @param text
+ * the item's text
+ * @param asHTML
+ * <code>true</code> to treat the specified text as html
+ * @param cmd
+ * the command to be fired when it is selected
+ */
+ public MenuItem(String text, boolean asHTML, Command cmd) {
+ this(text, asHTML);
+ setCommand(cmd);
+ }
+
+ /**
+ * Constructs a new menu item that cascades to a sub-menu when it is
+ * selected.
+ *
+ * @param text
+ * the item's text
+ * @param subMenu
+ * the sub-menu to be displayed when it is selected
+ */
+ public MenuItem(String text, MenuBar subMenu) {
+ this(text, false);
+ setSubMenu(subMenu);
+ }
+
+ /**
+ * Constructs a new menu item that cascades to a sub-menu when it is
+ * selected.
+ *
+ * @param text
+ * the item's text
+ * @param asHTML
+ * <code>true</code> to treat the specified text as html
+ * @param subMenu
+ * the sub-menu to be displayed when it is selected
+ */
+ public MenuItem(String text, boolean asHTML, MenuBar subMenu) {
+ this(text, asHTML);
+ setSubMenu(subMenu);
+ }
+
+ MenuItem(String text, boolean asHTML) {
+ setElement(DOM.createTD());
+ setSelectionStyle(false);
+
+ if (asHTML) {
+ setHTML(text);
+ } else {
+ setText(text);
+ }
+ setStyleName("gwt-MenuItem");
+ }
+
+ /**
+ * Gets the command associated with this item.
+ *
+ * @return this item's command, or <code>null</code> if none exists
+ */
+ public Command getCommand() {
+ return command;
+ }
+
+ public String getHTML() {
+ return DOM.getInnerHTML(getElement());
+ }
+
+ /**
+ * Gets the menu that contains this item.
+ *
+ * @return the parent menu, or <code>null</code> if none exists.
+ */
+ public MenuBar getParentMenu() {
+ return parentMenu;
+ }
+
+ /**
+ * Gets the sub-menu associated with this item.
+ *
+ * @return this item's sub-menu, or <code>null</code> if none exists
+ */
+ public MenuBar getSubMenu() {
+ return subMenu;
+ }
+
+ public String getText() {
+ return DOM.getInnerText(getElement());
+ }
+
+ /**
+ * Sets the command associated with this item.
+ *
+ * @param cmd
+ * the command to be associated with this item
+ */
+ public void setCommand(Command cmd) {
+ command = cmd;
+ }
+
+ public void setHTML(String html) {
+ DOM.setInnerHTML(getElement(), html);
+ }
+
+ /**
+ * Sets the sub-menu associated with this item.
+ *
+ * @param subMenu
+ * this item's new sub-menu
+ */
+ public void setSubMenu(MenuBar subMenu) {
+ this.subMenu = subMenu;
+ }
+
+ public void setText(String text) {
+ DOM.setInnerText(getElement(), text);
+ }
+
+ void setParentMenu(MenuBar parentMenu) {
+ this.parentMenu = parentMenu;
+ }
+
+ void setSelectionStyle(boolean selected) {
+ if (selected) {
+ addStyleDependentName(DEPENDENT_STYLENAME_SELECTED_ITEM);
+ } else {
+ removeStyleDependentName(DEPENDENT_STYLENAME_SELECTED_ITEM);
+ }
+ }
+}
--- /dev/null
+/* \r
+@ITMillApache2LicenseForJavaFiles@\r
+ */\r
+\r
+package com.itmill.toolkit.terminal.gwt.client.ui;\r
+\r
+import java.util.ArrayList;\r
+import java.util.EventObject;\r
+import java.util.Iterator;\r
+\r
+import com.google.gwt.user.client.DOM;\r
+import com.google.gwt.user.client.Element;\r
+import com.google.gwt.user.client.Event;\r
+import com.google.gwt.user.client.Timer;\r
+import com.google.gwt.user.client.ui.HTML;\r
+import com.google.gwt.user.client.ui.Widget;\r
+import com.itmill.toolkit.terminal.gwt.client.BrowserInfo;\r
+\r
+public class Notification extends ToolkitOverlay {\r
+\r
+ public static final int CENTERED = 1;\r
+ public static final int CENTERED_TOP = 2;\r
+ public static final int CENTERED_BOTTOM = 3;\r
+ public static final int TOP_LEFT = 4;\r
+ public static final int TOP_RIGHT = 5;\r
+ public static final int BOTTOM_LEFT = 6;\r
+ public static final int BOTTOM_RIGHT = 7;\r
+\r
+ public static final int DELAY_FOREVER = -1;\r
+ public static final int DELAY_NONE = 0;\r
+\r
+ private static final String STYLENAME = "i-Notification";\r
+ private static final int mouseMoveThreshold = 7;\r
+ private static final int Z_INDEX_BASE = 20000;\r
+\r
+ private int startOpacity = 90;\r
+ private int fadeMsec = 400;\r
+ private int delayMsec = 1000;\r
+\r
+ private Timer fader;\r
+ private Timer delay;\r
+\r
+ private int x = -1;\r
+ private int y = -1;\r
+\r
+ private String temporaryStyle;\r
+\r
+ private ArrayList listeners;\r
+\r
+ public Notification() {\r
+ setStylePrimaryName(STYLENAME);\r
+ sinkEvents(Event.ONCLICK);\r
+ DOM.setStyleAttribute(getElement(), "zIndex", "" + Z_INDEX_BASE);\r
+ }\r
+\r
+ public Notification(int delayMsec) {\r
+ this();\r
+ this.delayMsec = delayMsec;\r
+ }\r
+\r
+ public Notification(int delayMsec, int fadeMsec, int startOpacity) {\r
+ this(delayMsec);\r
+ this.fadeMsec = fadeMsec;\r
+ this.startOpacity = startOpacity;\r
+ }\r
+\r
+ public void startDelay() {\r
+ DOM.removeEventPreview(this);\r
+ if (delayMsec > 0) {\r
+ delay = new Timer() {\r
+ public void run() {\r
+ fade();\r
+ }\r
+ };\r
+ delay.scheduleRepeating(delayMsec);\r
+ } else if (delayMsec == 0) {\r
+ fade();\r
+ }\r
+ }\r
+\r
+ public void show() {\r
+ show(CENTERED);\r
+ }\r
+\r
+ public void show(String style) {\r
+ show(CENTERED, style);\r
+ }\r
+\r
+ public void show(int position) {\r
+ show(position, null);\r
+ }\r
+\r
+ public void show(Widget widget, int position, String style) {\r
+ setWidget(widget);\r
+ show(position, style);\r
+ }\r
+\r
+ public void show(String html, int position, String style) {\r
+ setWidget(new HTML(html));\r
+ show(position, style);\r
+ }\r
+\r
+ public void show(int position, String style) {\r
+ setOpacity(getElement(), startOpacity);\r
+ if (style != null) {\r
+ temporaryStyle = style;\r
+ addStyleName(style);\r
+ }\r
+ super.show();\r
+ setPosition(position);\r
+ }\r
+\r
+ public void hide() {\r
+ DOM.removeEventPreview(this);\r
+ cancelDelay();\r
+ cancelFade();\r
+ if (temporaryStyle != null) {\r
+ removeStyleName(temporaryStyle);\r
+ temporaryStyle = null;\r
+ }\r
+ super.hide();\r
+ fireEvent(new HideEvent(this));\r
+ }\r
+\r
+ public void fade() {\r
+ DOM.removeEventPreview(this);\r
+ cancelDelay();\r
+ fader = new Timer() {\r
+ int opacity = startOpacity;\r
+\r
+ public void run() {\r
+ opacity -= 5;\r
+ setOpacity(getElement(), opacity);\r
+ if (opacity <= 0) {\r
+ cancel();\r
+ hide();\r
+ if (BrowserInfo.get().isOpera()) {\r
+ // tray notification on opera needs to explicitly define\r
+ // size, reset it\r
+ DOM.setStyleAttribute(getElement(), "width", "");\r
+ DOM.setStyleAttribute(getElement(), "height", "");\r
+ }\r
+\r
+ }\r
+ }\r
+ };\r
+ final int msec = fadeMsec / (startOpacity / 5);\r
+ fader.scheduleRepeating(msec);\r
+ }\r
+\r
+ public void setPosition(int position) {\r
+ final Element el = getElement();\r
+ DOM.setStyleAttribute(el, "top", "");\r
+ DOM.setStyleAttribute(el, "left", "");\r
+ DOM.setStyleAttribute(el, "bottom", "");\r
+ DOM.setStyleAttribute(el, "right", "");\r
+ switch (position) {\r
+ case TOP_LEFT:\r
+ DOM.setStyleAttribute(el, "top", "0px");\r
+ DOM.setStyleAttribute(el, "left", "0px");\r
+ break;\r
+ case TOP_RIGHT:\r
+ DOM.setStyleAttribute(el, "top", "0px");\r
+ DOM.setStyleAttribute(el, "right", "0px");\r
+ break;\r
+ case BOTTOM_RIGHT:\r
+ DOM.setStyleAttribute(el, "position", "absolute");\r
+ if (BrowserInfo.get().isOpera()) {\r
+ // tray notification on opera needs explicitly defined size\r
+ DOM.setStyleAttribute(el, "width", getOffsetWidth() + "px");\r
+ DOM.setStyleAttribute(el, "height", getOffsetHeight() + "px");\r
+ }\r
+ DOM.setStyleAttribute(el, "bottom", "0px");\r
+ DOM.setStyleAttribute(el, "right", "0px");\r
+ break;\r
+ case BOTTOM_LEFT:\r
+ DOM.setStyleAttribute(el, "bottom", "0px");\r
+ DOM.setStyleAttribute(el, "left", "0px");\r
+ break;\r
+ case CENTERED_TOP:\r
+ center();\r
+ DOM.setStyleAttribute(el, "top", "0px");\r
+ break;\r
+ case CENTERED_BOTTOM:\r
+ center();\r
+ DOM.setStyleAttribute(el, "top", "");\r
+ DOM.setStyleAttribute(el, "bottom", "0px");\r
+ break;\r
+ default:\r
+ case CENTERED:\r
+ center();\r
+ break;\r
+ }\r
+ }\r
+\r
+ private void cancelFade() {\r
+ if (fader != null) {\r
+ fader.cancel();\r
+ fader = null;\r
+ }\r
+ }\r
+\r
+ private void cancelDelay() {\r
+ if (delay != null) {\r
+ delay.cancel();\r
+ delay = null;\r
+ }\r
+ }\r
+\r
+ private void setOpacity(Element el, int opacity) {\r
+ DOM.setStyleAttribute(el, "opacity", "" + (opacity / 100.0));\r
+ DOM.setStyleAttribute(el, "filter", "Alpha(opacity=" + opacity + ")");\r
+\r
+ }\r
+\r
+ public void onBrowserEvent(Event event) {\r
+ DOM.removeEventPreview(this);\r
+ if (fader == null) {\r
+ fade();\r
+ }\r
+ }\r
+\r
+ public boolean onEventPreview(Event event) {\r
+ int type = DOM.eventGetType(event);\r
+ // "modal"\r
+ if (delayMsec == -1) {\r
+ if (type == Event.ONCLICK\r
+ && DOM\r
+ .isOrHasChild(getElement(), DOM\r
+ .eventGetTarget(event))) {\r
+ fade();\r
+ }\r
+ return false;\r
+ }\r
+ // default\r
+ switch (type) {\r
+ case Event.ONMOUSEMOVE:\r
+\r
+ if (x < 0) {\r
+ x = DOM.eventGetClientX(event);\r
+ y = DOM.eventGetClientY(event);\r
+ } else if (Math.abs(DOM.eventGetClientX(event) - x) > mouseMoveThreshold\r
+ || Math.abs(DOM.eventGetClientY(event) - y) > mouseMoveThreshold) {\r
+ startDelay();\r
+ }\r
+ break;\r
+ case Event.ONCLICK:\r
+ case Event.ONDBLCLICK:\r
+ case Event.KEYEVENTS:\r
+ case Event.ONSCROLL:\r
+ default:\r
+ startDelay();\r
+ }\r
+ return true;\r
+ }\r
+\r
+ public void addEventListener(EventListener listener) {\r
+ if (listeners == null) {\r
+ listeners = new ArrayList();\r
+ }\r
+ listeners.add(listener);\r
+ }\r
+\r
+ public void removeEventListener(EventListener listener) {\r
+ if (listeners == null) {\r
+ return;\r
+ }\r
+ listeners.remove(listener);\r
+ }\r
+\r
+ private void fireEvent(HideEvent event) {\r
+ if (listeners != null) {\r
+ for (Iterator it = listeners.iterator(); it.hasNext();) {\r
+ EventListener l = (EventListener) it.next();\r
+ l.notificationHidden(event);\r
+ }\r
+ }\r
+ }\r
+\r
+ public class HideEvent extends EventObject {\r
+ public HideEvent(Object source) {\r
+ super(source);\r
+ }\r
+ }\r
+\r
+ public interface EventListener extends java.util.EventListener {\r
+ public void notificationHidden(HideEvent event);\r
+ }\r
+}\r
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client.ui;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.DeferredCommand;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.KeyboardListener;
+import com.google.gwt.user.client.ui.KeyboardListenerCollection;
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;
+import com.itmill.toolkit.terminal.gwt.client.UIDL;
+
+/**
+ * A helper class to implement keyboard shorcut handling. Keeps a list of owners
+ * actions and fires actions to server. User class needs to delegate keyboard
+ * events to handleKeyboardEvents function.
+ *
+ * @author IT Mill ltd
+ */
+public class ShortcutActionHandler {
+ private final ArrayList actions = new ArrayList();
+ private ApplicationConnection client;
+ private String paintableId;
+
+ /**
+ *
+ * @param pid
+ * Paintable id
+ * @param c
+ * reference to application connections
+ */
+ public ShortcutActionHandler(String pid, ApplicationConnection c) {
+ paintableId = pid;
+ client = c;
+ }
+
+ /**
+ * Updates list of actions this handler listens to.
+ *
+ * @param c
+ * UIDL snippet containing actions
+ */
+ public void updateActionMap(UIDL c) {
+ actions.clear();
+ final Iterator it = c.getChildIterator();
+ while (it.hasNext()) {
+ final UIDL action = (UIDL) it.next();
+
+ int[] modifiers = null;
+ if (action.hasAttribute("mk")) {
+ modifiers = action.getIntArrayAttribute("mk");
+ }
+
+ final ShortcutKeyCombination kc = new ShortcutKeyCombination(action
+ .getIntAttribute("kc"), modifiers);
+ final String key = action.getStringAttribute("key");
+ final String caption = action.getStringAttribute("caption");
+ actions.add(new ShortcutAction(key, kc, caption));
+ }
+ }
+
+ public void handleKeyboardEvent(Event event) {
+ final int modifiers = KeyboardListenerCollection
+ .getKeyboardModifiers(event);
+ final char keyCode = (char) DOM.eventGetKeyCode(event);
+ final ShortcutKeyCombination kc = new ShortcutKeyCombination(keyCode,
+ modifiers);
+ final Iterator it = actions.iterator();
+ while (it.hasNext()) {
+ final ShortcutAction a = (ShortcutAction) it.next();
+ if (a.getShortcutCombination().equals(kc)) {
+ shakeTarget(DOM.eventGetTarget(event));
+ DeferredCommand.addCommand(new Command() {
+ public void execute() {
+ client.updateVariable(paintableId, "action",
+ a.getKey(), true);
+ }
+ });
+ break;
+ }
+ }
+ }
+
+ public static native void shakeTarget(Element e)
+ /*-{
+ if(e.blur) {
+ e.blur();
+ e.focus();
+ }
+ }-*/;
+
+}
+
+class ShortcutKeyCombination {
+
+ public static final int SHIFT = 16;
+ public static final int CTRL = 17;
+ public static final int ALT = 18;
+
+ char keyCode = 0;
+ private int modifiersMask;
+
+ public ShortcutKeyCombination() {
+ }
+
+ ShortcutKeyCombination(char kc, int modifierMask) {
+ keyCode = kc;
+ modifiersMask = modifierMask;
+ }
+
+ ShortcutKeyCombination(int kc, int[] modifiers) {
+ keyCode = (char) kc;
+ keyCode = Character.toUpperCase(keyCode);
+
+ modifiersMask = 0;
+ if (modifiers != null) {
+ for (int i = 0; i < modifiers.length; i++) {
+ switch (modifiers[i]) {
+ case ALT:
+ modifiersMask = modifiersMask
+ | KeyboardListener.MODIFIER_ALT;
+ break;
+ case CTRL:
+ modifiersMask = modifiersMask
+ | KeyboardListener.MODIFIER_CTRL;
+ break;
+ case SHIFT:
+ modifiersMask = modifiersMask
+ | KeyboardListener.MODIFIER_SHIFT;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+
+ public boolean equals(ShortcutKeyCombination other) {
+ if (keyCode == other.keyCode && modifiersMask == other.modifiersMask) {
+ return true;
+ }
+ return false;
+ }
+}
+
+class ShortcutAction {
+
+ private final ShortcutKeyCombination sc;
+ private final String caption;
+ private final String key;
+
+ public ShortcutAction(String key, ShortcutKeyCombination sc, String caption) {
+ this.sc = sc;
+ this.key = key;
+ this.caption = caption;
+ }
+
+ public ShortcutKeyCombination getShortcutCombination() {
+ return sc;
+ }
+
+ public String getCaption() {
+ return caption;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client.ui;
+
+import com.google.gwt.user.client.ui.HasWidgets;
+import com.itmill.toolkit.terminal.gwt.client.Paintable;
+
+public interface Table extends Paintable, HasWidgets {
+ final int SELECT_MODE_NONE = 0;
+ final int SELECT_MODE_SINGLE = 1;
+ final int SELECT_MODE_MULTI = 2;
+
+}
--- /dev/null
+/* \r
+@ITMillApache2LicenseForJavaFiles@\r
+ */\r
+\r
+package com.itmill.toolkit.terminal.gwt.client.ui;\r
+\r
+import java.util.Date;\r
+\r
+import com.google.gwt.user.client.ui.ChangeListener;\r
+import com.google.gwt.user.client.ui.FlowPanel;\r
+import com.google.gwt.user.client.ui.ListBox;\r
+import com.google.gwt.user.client.ui.Widget;\r
+\r
+public class Time extends FlowPanel implements ChangeListener {\r
+\r
+ private final IDateField datefield;\r
+\r
+ private ListBox hours;\r
+\r
+ private ListBox mins;\r
+\r
+ private ListBox sec;\r
+\r
+ private ListBox msec;\r
+\r
+ private ListBox ampm;\r
+\r
+ private int resolution = IDateField.RESOLUTION_HOUR;\r
+\r
+ private boolean readonly;\r
+\r
+ public Time(IDateField parent) {\r
+ super();\r
+ datefield = parent;\r
+ setStyleName(IDateField.CLASSNAME + "-time");\r
+ }\r
+\r
+ private void buildTime(boolean redraw) {\r
+ final boolean thc = datefield.getDateTimeService().isTwelveHourClock();\r
+ if (redraw) {\r
+ clear();\r
+ final int numHours = thc ? 12 : 24;\r
+ hours = new ListBox();\r
+ hours.setStyleName(INativeSelect.CLASSNAME);\r
+ for (int i = 0; i < numHours; i++) {\r
+ hours.addItem((i < 10) ? "0" + i : "" + i);\r
+ }\r
+ hours.addChangeListener(this);\r
+ if (thc) {\r
+ ampm = new ListBox();\r
+ ampm.setStyleName(INativeSelect.CLASSNAME);\r
+ final String[] ampmText = datefield.getDateTimeService()\r
+ .getAmPmStrings();\r
+ ampm.addItem(ampmText[0]);\r
+ ampm.addItem(ampmText[1]);\r
+ ampm.addChangeListener(this);\r
+ }\r
+\r
+ if (datefield.getCurrentResolution() >= IDateField.RESOLUTION_MIN) {\r
+ mins = new ListBox();\r
+ mins.setStyleName(INativeSelect.CLASSNAME);\r
+ for (int i = 0; i < 60; i++) {\r
+ mins.addItem((i < 10) ? "0" + i : "" + i);\r
+ }\r
+ mins.addChangeListener(this);\r
+ }\r
+ if (datefield.getCurrentResolution() >= IDateField.RESOLUTION_SEC) {\r
+ sec = new ListBox();\r
+ sec.setStyleName(INativeSelect.CLASSNAME);\r
+ for (int i = 0; i < 60; i++) {\r
+ sec.addItem((i < 10) ? "0" + i : "" + i);\r
+ }\r
+ sec.addChangeListener(this);\r
+ }\r
+ if (datefield.getCurrentResolution() == IDateField.RESOLUTION_MSEC) {\r
+ msec = new ListBox();\r
+ msec.setStyleName(INativeSelect.CLASSNAME);\r
+ for (int i = 0; i < 1000; i++) {\r
+ if (i < 10) {\r
+ msec.addItem("00" + i);\r
+ } else if (i < 100) {\r
+ msec.addItem("0" + i);\r
+ } else {\r
+ msec.addItem("" + i);\r
+ }\r
+ }\r
+ msec.addChangeListener(this);\r
+ }\r
+\r
+ final String delimiter = datefield.getDateTimeService()\r
+ .getClockDelimeter();\r
+ final boolean ro = datefield.isReadonly();\r
+\r
+ if (ro) {\r
+ int h = 0;\r
+ if (datefield.getCurrentDate() != null) {\r
+ h = datefield.getCurrentDate().getHours();\r
+ }\r
+ if (thc) {\r
+ h -= h < 12 ? 0 : 12;\r
+ }\r
+ add(new ILabel(h < 10 ? "0" + h : "" + h));\r
+ } else {\r
+ add(hours);\r
+ }\r
+\r
+ if (datefield.getCurrentResolution() >= IDateField.RESOLUTION_MIN) {\r
+ add(new ILabel(delimiter));\r
+ if (ro) {\r
+ final int m = mins.getSelectedIndex();\r
+ add(new ILabel(m < 10 ? "0" + m : "" + m));\r
+ } else {\r
+ add(mins);\r
+ }\r
+ }\r
+ if (datefield.getCurrentResolution() >= IDateField.RESOLUTION_SEC) {\r
+ add(new ILabel(delimiter));\r
+ if (ro) {\r
+ final int s = sec.getSelectedIndex();\r
+ add(new ILabel(s < 10 ? "0" + s : "" + s));\r
+ } else {\r
+ add(sec);\r
+ }\r
+ }\r
+ if (datefield.getCurrentResolution() == IDateField.RESOLUTION_MSEC) {\r
+ add(new ILabel("."));\r
+ if (ro) {\r
+ final int m = datefield.getMilliseconds();\r
+ final String ms = m < 100 ? "0" + m : "" + m;\r
+ add(new ILabel(m < 10 ? "0" + ms : ms));\r
+ } else {\r
+ add(msec);\r
+ }\r
+ }\r
+ if (datefield.getCurrentResolution() == IDateField.RESOLUTION_HOUR) {\r
+ add(new ILabel(delimiter + "00")); // o'clock\r
+ }\r
+ if (thc) {\r
+ add(new ILabel(" "));\r
+ if (ro) {\r
+ add(new ILabel(ampm.getItemText(datefield.getCurrentDate()\r
+ .getHours() < 12 ? 0 : 1)));\r
+ } else {\r
+ add(ampm);\r
+ }\r
+ }\r
+\r
+ if (ro) {\r
+ return;\r
+ }\r
+ }\r
+\r
+ // Update times\r
+ Date cdate = datefield.getCurrentDate();\r
+ boolean selected = true;\r
+ if (cdate == null) {\r
+ cdate = new Date();\r
+ selected = false;\r
+ }\r
+ if (thc) {\r
+ int h = cdate.getHours();\r
+ ampm.setSelectedIndex(h < 12 ? 0 : 1);\r
+ h -= ampm.getSelectedIndex() * 12;\r
+ hours.setSelectedIndex(h);\r
+ } else {\r
+ hours.setSelectedIndex(cdate.getHours());\r
+ }\r
+ if (datefield.getCurrentResolution() >= IDateField.RESOLUTION_MIN) {\r
+ mins.setSelectedIndex(cdate.getMinutes());\r
+ }\r
+ if (datefield.getCurrentResolution() >= IDateField.RESOLUTION_SEC) {\r
+ sec.setSelectedIndex(cdate.getSeconds());\r
+ }\r
+ if (datefield.getCurrentResolution() == IDateField.RESOLUTION_MSEC) {\r
+ if (selected) {\r
+ msec.setSelectedIndex(datefield.getMilliseconds());\r
+ } else {\r
+ msec.setSelectedIndex(0);\r
+ }\r
+ }\r
+ if (thc) {\r
+ ampm.setSelectedIndex(cdate.getHours() < 12 ? 0 : 1);\r
+ }\r
+\r
+ if (datefield.isReadonly() && !redraw) {\r
+ // Do complete redraw when in read-only status\r
+ clear();\r
+ final String delimiter = datefield.getDateTimeService()\r
+ .getClockDelimeter();\r
+\r
+ int h = cdate.getHours();\r
+ if (thc) {\r
+ h -= h < 12 ? 0 : 12;\r
+ }\r
+ add(new ILabel(h < 10 ? "0" + h : "" + h));\r
+\r
+ if (datefield.getCurrentResolution() >= IDateField.RESOLUTION_MIN) {\r
+ add(new ILabel(delimiter));\r
+ final int m = mins.getSelectedIndex();\r
+ add(new ILabel(m < 10 ? "0" + m : "" + m));\r
+ }\r
+ if (datefield.getCurrentResolution() >= IDateField.RESOLUTION_SEC) {\r
+ add(new ILabel(delimiter));\r
+ final int s = sec.getSelectedIndex();\r
+ add(new ILabel(s < 10 ? "0" + s : "" + s));\r
+ }\r
+ if (datefield.getCurrentResolution() == IDateField.RESOLUTION_MSEC) {\r
+ add(new ILabel("."));\r
+ final int m = datefield.getMilliseconds();\r
+ final String ms = m < 100 ? "0" + m : "" + m;\r
+ add(new ILabel(m < 10 ? "0" + ms : ms));\r
+ }\r
+ if (datefield.getCurrentResolution() == IDateField.RESOLUTION_HOUR) {\r
+ add(new ILabel(delimiter + "00")); // o'clock\r
+ }\r
+ if (thc) {\r
+ add(new ILabel(" "));\r
+ add(new ILabel(ampm.getItemText(cdate.getHours() < 12 ? 0 : 1)));\r
+ }\r
+ }\r
+\r
+ final boolean enabled = datefield.isEnabled();\r
+ hours.setEnabled(enabled);\r
+ if (mins != null) {\r
+ mins.setEnabled(enabled);\r
+ }\r
+ if (sec != null) {\r
+ sec.setEnabled(enabled);\r
+ }\r
+ if (msec != null) {\r
+ msec.setEnabled(enabled);\r
+ }\r
+ if (ampm != null) {\r
+ ampm.setEnabled(enabled);\r
+ }\r
+\r
+ }\r
+\r
+ public void updateTime(boolean redraw) {\r
+ buildTime(redraw || resolution != datefield.getCurrentResolution()\r
+ || readonly != datefield.isReadonly());\r
+ if (datefield instanceof ITextualDate) {\r
+ ((ITextualDate) datefield).buildDate();\r
+ }\r
+ resolution = datefield.getCurrentResolution();\r
+ readonly = datefield.isReadonly();\r
+ }\r
+\r
+ public void onChange(Widget sender) {\r
+ if (datefield.getCurrentDate() == null) {\r
+ // was null on server, need to set\r
+ Date now = datefield.getShowingDate();\r
+ if (now == null) {\r
+ now = new Date();\r
+ datefield.setShowingDate(now);\r
+ }\r
+ datefield.setCurrentDate(new Date(now.getTime()));\r
+\r
+ // Init variables with current time\r
+ datefield.getClient().updateVariable(datefield.getId(), "year",\r
+ now.getYear() + 1900, false);\r
+ datefield.getClient().updateVariable(datefield.getId(), "month",\r
+ now.getMonth() + 1, false);\r
+ datefield.getClient().updateVariable(datefield.getId(), "day",\r
+ now.getDate(), false);\r
+ datefield.getClient().updateVariable(datefield.getId(), "hour",\r
+ now.getHours(), false);\r
+ datefield.getClient().updateVariable(datefield.getId(), "min",\r
+ now.getMinutes(), false);\r
+ datefield.getClient().updateVariable(datefield.getId(), "sec",\r
+ now.getSeconds(), false);\r
+ datefield.getClient().updateVariable(datefield.getId(), "msec",\r
+ datefield.getMilliseconds(), false);\r
+ }\r
+ if (sender == hours) {\r
+ int h = hours.getSelectedIndex();\r
+ if (datefield.getDateTimeService().isTwelveHourClock()) {\r
+ h = h + ampm.getSelectedIndex() * 12;\r
+ }\r
+ datefield.getCurrentDate().setHours(h);\r
+ datefield.getShowingDate().setHours(h);\r
+ datefield.getClient().updateVariable(datefield.getId(), "hour", h,\r
+ datefield.isImmediate());\r
+ updateTime(false);\r
+ } else if (sender == mins) {\r
+ final int m = mins.getSelectedIndex();\r
+ datefield.getCurrentDate().setMinutes(m);\r
+ datefield.getShowingDate().setMinutes(m);\r
+ datefield.getClient().updateVariable(datefield.getId(), "min", m,\r
+ datefield.isImmediate());\r
+ updateTime(false);\r
+ } else if (sender == sec) {\r
+ final int s = sec.getSelectedIndex();\r
+ datefield.getCurrentDate().setSeconds(s);\r
+ datefield.getShowingDate().setSeconds(s);\r
+ datefield.getClient().updateVariable(datefield.getId(), "sec", s,\r
+ datefield.isImmediate());\r
+ updateTime(false);\r
+ } else if (sender == msec) {\r
+ final int ms = msec.getSelectedIndex();\r
+ datefield.setMilliseconds(ms);\r
+ datefield.setShowingMilliseconds(ms);\r
+ datefield.getClient().updateVariable(datefield.getId(), "msec", ms,\r
+ datefield.isImmediate());\r
+ updateTime(false);\r
+ } else if (sender == ampm) {\r
+ final int h = hours.getSelectedIndex() + ampm.getSelectedIndex()\r
+ * 12;\r
+ datefield.getCurrentDate().setHours(h);\r
+ datefield.getShowingDate().setHours(h);\r
+ datefield.getClient().updateVariable(datefield.getId(), "hour", h,\r
+ datefield.isImmediate());\r
+ updateTime(false);\r
+ }\r
+ }\r
+\r
+}\r
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client.ui;
+
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.ui.PopupPanel;
+
+/**
+ * In Toolkit UI this Overlay should always be used for all elements that
+ * temporary float over other components like context menus etc. This is to deal
+ * stacking order correctly with IWindow objects.
+ */
+public class ToolkitOverlay extends PopupPanel {
+
+ public static final int Z_INDEX = 20000;
+
+ public ToolkitOverlay() {
+ super();
+ adjustZIndex();
+ }
+
+ public ToolkitOverlay(boolean autoHide) {
+ super(autoHide);
+ adjustZIndex();
+ }
+
+ public ToolkitOverlay(boolean autoHide, boolean modal) {
+ super(autoHide, modal);
+ adjustZIndex();
+ }
+
+ private void adjustZIndex() {
+ DOM.setStyleAttribute(getElement(), "zIndex", "" + (Z_INDEX));
+ }
+
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client.ui;
+
+/**
+ * This class is used for "row actions" in ITree and ITable
+ */
+public class TreeAction extends Action {
+
+ String targetKey = "";
+ String actionKey = "";
+
+ public TreeAction(ActionOwner owner) {
+ super(owner);
+ }
+
+ public TreeAction(ActionOwner owner, String target, String action) {
+ this(owner);
+ targetKey = target;
+ actionKey = action;
+ }
+
+ /**
+ * Sends message to server that this action has been fired. Messages are
+ * "standard" Toolkit messages whose value is comma separated pair of
+ * targetKey (row, treeNod ...) and actions id.
+ *
+ * Variablename is always "action".
+ *
+ * Actions are always sent immediatedly to server.
+ */
+ public void execute() {
+ owner.getClient().updateVariable(owner.getPaintableId(), "action",
+ targetKey + "," + actionKey, true);
+ owner.getClient().getContextMenu().hide();
+ }
+
+ public String getActionKey() {
+ return actionKey;
+ }
+
+ public void setActionKey(String actionKey) {
+ this.actionKey = actionKey;
+ }
+
+ public String getTargetKey() {
+ return targetKey;
+ }
+
+ public void setTargetKey(String targetKey) {
+ this.targetKey = targetKey;
+ }
+}
--- /dev/null
+/* \r
+@ITMillApache2LicenseForJavaFiles@\r
+ */\r
+\r
+package com.itmill.toolkit.terminal.gwt.client.ui;\r
+\r
+import com.google.gwt.user.client.ui.AbstractImagePrototype;\r
+\r
+public interface TreeImages extends com.google.gwt.user.client.ui.TreeImages {\r
+\r
+ /**\r
+ * An image indicating an open branch.\r
+ * \r
+ * @return a prototype of this image\r
+ * @gwt.resource com/itmill/toolkit/terminal/gwt/public/default/tree/img/expanded.png\r
+ */\r
+ AbstractImagePrototype treeOpen();\r
+\r
+ /**\r
+ * An image indicating a closed branch.\r
+ * \r
+ * @return a prototype of this image\r
+ * @gwt.resource com/itmill/toolkit/terminal/gwt/public/default/tree/img/collapsed.png\r
+ */\r
+ AbstractImagePrototype treeClosed();\r
+\r
+}\r
--- /dev/null
+package com.itmill.toolkit.terminal.gwt.client.ui.absolutegrid;
+
+import java.util.HashMap;
+import java.util.Iterator;
+
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.ui.AbsolutePanel;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.itmill.toolkit.terminal.gwt.client.Caption;
+import com.itmill.toolkit.terminal.gwt.client.ContainerResizedListener;
+import com.itmill.toolkit.terminal.gwt.client.Util;
+import com.itmill.toolkit.terminal.gwt.client.ui.AlignmentInfo;
+
+/**
+ * Prototype helper widget to implement complex sized Toolkit layouts like
+ * GridLayout and OrderedLayout. Supports size, margins, spacing, but has bit
+ * expensive layout function.
+ */
+public class AbsoluteGrid extends Composite implements ContainerResizedListener {
+
+ protected HashMap cells = new HashMap();
+
+ private int cols = 1;
+ private int rows = 1;
+
+ private AbsolutePanel ap;
+
+ protected int marginTop;
+ protected int marginBottom;
+ protected int marginLeft;
+ protected int marginRight;
+
+ private int offsetWidth;
+
+ private int offsetHeight;
+
+ public AbsoluteGrid() {
+ ap = new AbsolutePanel();
+ initWidget(ap);
+ }
+
+ public AbsoluteGridCell getCell(int col, int row) {
+ AbsoluteGridCell p = (AbsoluteGridCell) cells.get(col + "." + row);
+ if (p == null) {
+ p = new AbsoluteGridCell(col, row);
+ cells.put(col + "." + row, p);
+ ap.add(p);
+ }
+ return p;
+ }
+
+ public void clear() {
+ ap.clear();
+ cells.clear();
+ }
+
+ public Iterator getCellIterator() {
+ return cells.values().iterator();
+ }
+
+ private float getCellWidth(int colspan) {
+ int total = ap.getOffsetWidth();
+ total -= getMarginWidth();
+ total -= getSpacingSize() * (cols - colspan);
+ if (total < 0) {
+ return 0;
+ }
+ return total * colspan / (float) cols;
+ }
+
+ /**
+ *
+ * @return space used by left and right margin
+ */
+ private int getMarginWidth() {
+ return marginLeft + marginRight;
+ }
+
+ /**
+ * @return pixels reserved for space between components
+ */
+ protected int getSpacingSize() {
+ return 0;
+ }
+
+ private float getCellHeight(int rowspan) {
+ int total = ap.getOffsetHeight();
+ total -= getMarginHeight();
+ total -= getSpacingSize() * (rows - rowspan);
+ if (total < 0) {
+ return 0;
+ }
+ return total * rowspan / (float) rows;
+ }
+
+ /**
+ *
+ * @return space used by top and bottom margin
+ */
+ private int getMarginHeight() {
+ return marginBottom + marginTop;
+ }
+
+ /**
+ * TODO contains Caption (which is a widget) in a very bad way, cannot be
+ * simple panel
+ */
+ public class AbsoluteGridCell extends SimplePanel {
+
+ int rowIndex;
+ int colIndex;
+ int colSpan = 1;
+ int rowSpan = 1;
+ private Element container = DOM.createDiv();
+
+ private Caption caption;
+ private AlignmentInfo alignmentInfo = new AlignmentInfo(
+ AlignmentInfo.ALIGNMENT_TOP + AlignmentInfo.ALIGNMENT_LEFT);
+
+ AbsoluteGridCell(int colIndex, int rowIndex) {
+ super();
+ DOM.appendChild(getElement(), container);
+ this.rowIndex = rowIndex;
+ this.colIndex = colIndex;
+ }
+
+ public void clear() {
+ super.clear();
+ if (caption != null) {
+ DOM.removeChild(getElement(), caption.getElement());
+ caption = null;
+ }
+ }
+
+ protected Element getContainerElement() {
+ return container;
+ }
+
+ void setColSpan(int s) {
+ // TODO Should remove possibly collapsing cells
+ colSpan = s;
+ }
+
+ void setRowSpan(int s) {
+ // TODO Should remove possibly collapsing cells
+ rowSpan = s;
+ }
+
+ private int getLeft() {
+ int left = marginLeft;
+ left += colIndex * getCellWidth(1);
+ left += getSpacingSize() * colIndex;
+ return left;
+ }
+
+ private int getTop() {
+ int top = marginTop;
+ top += rowIndex * getCellHeight(1);
+ top += getSpacingSize() * rowIndex;
+ return top;
+ }
+
+ public void render() {
+ setPixelSize((int) getCellWidth(colSpan),
+ (int) getCellHeight(rowSpan));
+ ap.setWidgetPosition(this, getLeft(), getTop());
+ }
+
+ /**
+ * Does vertical positioning based on DOM values
+ */
+ public void vAling() {
+ DOM.setStyleAttribute(getElement(), "paddingTop", "0");
+ if (!alignmentInfo.isTop()) {
+ Widget c = getWidget();
+ if (c != null) {
+
+ int oh = getOffsetHeight();
+ int wt = DOM.getElementPropertyInt(container, "offsetTop");
+ int wh = c.getOffsetHeight();
+
+ int freeSpace = getOffsetHeight()
+ - (DOM
+ .getElementPropertyInt(container,
+ "offsetTop") + c.getOffsetHeight());
+ if (Util.isIE()) {
+ freeSpace -= DOM.getElementPropertyInt(c.getElement(),
+ "offsetTop");
+ }
+ if (freeSpace < 0) {
+ freeSpace = 0; // clipping rest of contents when object
+ // larger than reserved area
+ }
+ if (alignmentInfo.isVerticalCenter()) {
+ DOM.setStyleAttribute(getElement(), "paddingTop",
+ (freeSpace / 2) + "px");
+ } else {
+ DOM.setStyleAttribute(getElement(), "paddingTop",
+ (freeSpace) + "px");
+ }
+ }
+ }
+ }
+
+ public void setPixelSize(int width, int height) {
+ super.setPixelSize(width, height);
+ DOM.setStyleAttribute(container, "width", width + "px");
+ int contHeight = height - getCaptionHeight();
+ if (contHeight < 0) {
+ contHeight = 0;
+ }
+ DOM.setStyleAttribute(container, "height", contHeight + "px");
+ }
+
+ private int getCaptionHeight() {
+ // remove hard coded caption height
+ return (caption == null) ? 0 : caption.getOffsetHeight();
+ }
+
+ public Caption getCaption() {
+ return caption;
+ }
+
+ public void setCaption(Caption newCaption) {
+ // TODO check for existing, shouldn't happen though
+ caption = newCaption;
+ DOM.insertChild(getElement(), caption.getElement(), 0);
+ }
+
+ public void setAlignment(int bitmask) {
+ if (alignmentInfo.getBitMask() != bitmask) {
+ alignmentInfo = new AlignmentInfo(bitmask);
+ setHorizontalAling();
+ // vertical align is set in render() method
+ }
+ }
+
+ private void setHorizontalAling() {
+ DOM.setStyleAttribute(getElement(), "textAlign", alignmentInfo
+ .getHorizontalAlignment());
+ if (getWidget() != null) {
+ Element el = getWidget().getElement();
+ if (alignmentInfo.isHorizontalCenter()
+ || alignmentInfo.isRight()) {
+ DOM.setStyleAttribute(el, "marginLeft", "auto");
+ } else {
+ DOM.setStyleAttribute(el, "marginLeft", "");
+ }
+ if (alignmentInfo.isHorizontalCenter()
+ || alignmentInfo.isLeft()) {
+ DOM.setStyleAttribute(el, "marginRight", "auto");
+ } else {
+ DOM.setStyleAttribute(el, "marginRight", "");
+ }
+ }
+ }
+ }
+
+ public void iLayout() {
+ boolean sizeChanged = false;
+ int newWidth = getOffsetWidth();
+ if (offsetWidth != newWidth) {
+ offsetWidth = newWidth;
+ sizeChanged = true;
+ }
+ int newHeight = getOffsetHeight();
+ if (offsetHeight != newHeight) {
+ offsetHeight = newHeight;
+ sizeChanged = true;
+ }
+ if (sizeChanged) {
+ for (Iterator it = cells.values().iterator(); it.hasNext();) {
+ AbsoluteGridCell cell = (AbsoluteGridCell) it.next();
+ cell.render();
+ cell.vAling();
+ }
+ Util.runDescendentsLayout(ap);
+ }
+ }
+
+ public int getCols() {
+ return cols;
+ }
+
+ public void setCols(int cols) {
+ this.cols = cols;
+ // force relayout
+ offsetHeight = 0;
+ offsetWidth = 0;
+ }
+
+ public int getRows() {
+ return rows;
+ }
+
+ public void setRows(int rows) {
+ this.rows = rows;
+ // force relayout
+ offsetHeight = 0;
+ offsetWidth = 0;
+ }
+}
--- /dev/null
+package com.itmill.toolkit.terminal.gwt.client.ui.absolutegrid;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+
+import com.google.gwt.user.client.ui.Widget;
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;
+import com.itmill.toolkit.terminal.gwt.client.Caption;
+import com.itmill.toolkit.terminal.gwt.client.Container;
+import com.itmill.toolkit.terminal.gwt.client.Paintable;
+import com.itmill.toolkit.terminal.gwt.client.UIDL;
+import com.itmill.toolkit.terminal.gwt.client.ui.MarginInfo;
+
+/**
+ * Proto level implementation of GridLayout.
+ *
+ * All cell's will be equally sized.
+ *
+ */
+public class ISizeableGridLayout extends AbsoluteGrid implements Paintable,
+ Container {
+ public static final String CLASSNAME = "i-gridlayout";
+ private int spacing;
+ private HashMap paintableToCellMap = new HashMap();
+ private ApplicationConnection client;
+
+ public ISizeableGridLayout() {
+ super();
+ setStyleName(CLASSNAME);
+ }
+
+ protected int getSpacingSize() {
+ return spacing;
+ }
+
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+ this.client = client;
+
+ if (client.updateComponent(this, uidl, false)) {
+ return;
+ }
+
+ if (uidl.hasAttribute("caption")) {
+ setTitle(uidl.getStringAttribute("caption"));
+ }
+ int row = 0, column = 0;
+
+ final ArrayList oldCells = new ArrayList();
+ for (final Iterator iterator = getCellIterator(); iterator.hasNext();) {
+ oldCells.add(iterator.next());
+ }
+ clear();
+
+ setCols(uidl.getIntAttribute("w"));
+ setRows(uidl.getIntAttribute("h"));
+
+ handleMargins(uidl);
+ spacing = uidl.getBooleanAttribute("spacing") ? detectSpacingSize() : 0;
+
+ final int[] alignments = uidl.getIntArrayAttribute("alignments");
+ int alignmentIndex = 0;
+
+ for (final Iterator i = uidl.getChildIterator(); i.hasNext();) {
+ final UIDL r = (UIDL) i.next();
+ if ("gr".equals(r.getTag())) {
+ column = 0;
+ for (final Iterator j = r.getChildIterator(); j.hasNext();) {
+ final UIDL c = (UIDL) j.next();
+ if ("gc".equals(c.getTag())) {
+
+ // Set cell width
+ int colSpan;
+ if (c.hasAttribute("w")) {
+ colSpan = c.getIntAttribute("w");
+ } else {
+ colSpan = 1;
+ }
+
+ // Set cell height
+ int rowSpan;
+ if (c.hasAttribute("h")) {
+ rowSpan = c.getIntAttribute("h");
+ } else {
+ rowSpan = 1;
+ }
+
+ final UIDL u = c.getChildUIDL(0);
+ if (u != null) {
+ final Paintable child = client.getPaintable(u);
+ AbsoluteGridCell cell = getCell(column, row);
+ paintableToCellMap.put(child, cell);
+ cell.rowSpan = rowSpan;
+ cell.colSpan = colSpan;
+
+ oldCells.remove(cell);
+
+ cell.setAlignment(alignments[alignmentIndex++]);
+
+ cell.render();
+
+ cell.setWidget((Widget) child);
+
+ if (!u.getBooleanAttribute("cached")) {
+ child.updateFromUIDL(u, client);
+ }
+
+ cell.vAling();
+ }
+ column += colSpan;
+ }
+ }
+ row++;
+ }
+ }
+
+ // loop oldWidgetWrappers that where not re-attached and unregister them
+ for (final Iterator it = oldCells.iterator(); it.hasNext();) {
+ final AbsoluteGridCell w = (AbsoluteGridCell) it.next();
+ client.unregisterPaintable((Paintable) w.getWidget());
+ w.removeFromParent();
+ paintableToCellMap.remove(w.getWidget());
+ }
+
+ }
+
+ protected void handleMargins(UIDL uidl) {
+ final MarginInfo margins = new MarginInfo(uidl
+ .getIntAttribute("margins"));
+ // TODO build CSS detector to make margins configurable through css
+ marginTop = margins.hasTop() ? 15 : 0;
+ marginRight = margins.hasRight() ? 15 : 0;
+ marginBottom = margins.hasBottom() ? 15 : 0;
+ marginLeft = margins.hasLeft() ? 15 : 0;
+ }
+
+ private int detectSpacingSize() {
+ // TODO Auto-generated method stub
+ return 15;
+ }
+
+ public boolean hasChildComponent(Widget component) {
+ if (paintableToCellMap.containsKey(component)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public void replaceChildComponent(Widget oldComponent, Widget newComponent) {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void updateCaption(Paintable component, UIDL uidl) {
+ AbsoluteGridCell cell = (AbsoluteGridCell) paintableToCellMap
+ .get(component);
+ Caption c = cell.getCaption();
+ if (c == null) {
+ c = new Caption(component, client);
+ cell.setCaption(c);
+ }
+ c.updateCaption(uidl);
+ }
+
+}
--- /dev/null
+package com.itmill.toolkit.terminal.gwt.client.ui.absolutegrid;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+
+import com.google.gwt.user.client.ui.Widget;
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;
+import com.itmill.toolkit.terminal.gwt.client.Caption;
+import com.itmill.toolkit.terminal.gwt.client.Container;
+import com.itmill.toolkit.terminal.gwt.client.Paintable;
+import com.itmill.toolkit.terminal.gwt.client.UIDL;
+import com.itmill.toolkit.terminal.gwt.client.ui.MarginInfo;
+
+/**
+ * Proto level implementation of GridLayout.
+ *
+ * All cell's will be equally sized.
+ *
+ */
+public class ISizeableOrderedLayout extends AbsoluteGrid implements Paintable,
+ Container {
+ public static final String CLASSNAME = "i-orderedlayout";
+ private static final int ORIENTETION_HORIZONTAL = 1;
+ private int spacing;
+ private HashMap paintableToCellMap = new HashMap();
+ private ApplicationConnection client;
+ private int orientation;
+
+ public ISizeableOrderedLayout() {
+ super();
+ setStyleName(CLASSNAME);
+ }
+
+ protected int getSpacingSize() {
+ return spacing;
+ }
+
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+ this.client = client;
+
+ if (client.updateComponent(this, uidl, false)) {
+ return;
+ }
+
+ orientation = (uidl.hasAttribute("orientation") ? ORIENTETION_HORIZONTAL
+ : 0);
+
+ if (uidl.hasAttribute("caption")) {
+ setTitle(uidl.getStringAttribute("caption"));
+ }
+
+ handleMargins(uidl);
+ spacing = uidl.getBooleanAttribute("spacing") ? detectSpacingSize() : 0;
+
+ final int[] alignments = uidl.getIntArrayAttribute("alignments");
+ int alignmentIndex = 0;
+
+ // Update contained components
+
+ final ArrayList uidlWidgets = new ArrayList();
+ for (final Iterator it = uidl.getChildIterator(); it.hasNext();) {
+ final UIDL uidlForChild = (UIDL) it.next();
+ final Paintable child = client.getPaintable(uidlForChild);
+ uidlWidgets.add(child);
+ }
+
+ if (orientation == ORIENTETION_HORIZONTAL) {
+ setCols(uidlWidgets.size());
+ setRows(1);
+ } else {
+ setCols(1);
+ setRows(uidlWidgets.size());
+ }
+
+ final ArrayList oldWidgets = getPaintables();
+
+ final HashMap oldCaptions = new HashMap();
+
+ final Iterator newIt = uidlWidgets.iterator();
+ final Iterator newUidl = uidl.getChildIterator();
+
+ int row = 0, column = 0;
+ while (newIt.hasNext()) {
+ final Widget child = (Widget) newIt.next();
+ final UIDL childUidl = (UIDL) newUidl.next();
+
+ AbsoluteGridCell cell = getCell(column, row);
+
+ Widget oldChild = cell.getWidget();
+ if (oldChild != null) {
+ if (oldChild != child) {
+ oldCaptions.put(oldChild, cell.getCaption());
+ cell.clear();
+ cell.setWidget(child);
+ paintableToCellMap.remove(oldChild);
+ Caption newCaption = (Caption) oldCaptions.get(child);
+ if (newCaption == null) {
+ newCaption = new Caption((Paintable) child, client);
+ }
+ cell.setCaption(newCaption);
+ }
+ } else {
+ cell.setWidget(child);
+ }
+
+ paintableToCellMap.put(child, cell);
+
+ cell.setAlignment(alignments[alignmentIndex++]);
+
+ cell.render();
+
+ ((Paintable) child).updateFromUIDL(childUidl, client);
+
+ cell.vAling();
+
+ if (orientation == ORIENTETION_HORIZONTAL) {
+ column++;
+ } else {
+ row++;
+ }
+ oldWidgets.remove(child);
+ }
+ // remove possibly remaining old Paintable object which were not updated
+ Iterator oldIt = oldWidgets.iterator();
+ while (oldIt.hasNext()) {
+ final Paintable p = (Paintable) oldIt.next();
+ if (!uidlWidgets.contains(p)) {
+ removePaintable(p);
+ }
+ }
+ }
+
+ private void removePaintable(Paintable oldChild) {
+ AbsoluteGridCell cell = (AbsoluteGridCell) paintableToCellMap
+ .get(oldChild);
+ if (cell != null) {
+ cell.clear();
+ }
+ client.unregisterPaintable(oldChild);
+ }
+
+ private ArrayList getPaintables() {
+ ArrayList paintables = new ArrayList();
+ Iterator it = paintableToCellMap.keySet().iterator();
+ while (it.hasNext()) {
+ Paintable p = (Paintable) it.next();
+ paintables.add(p);
+ }
+ return paintables;
+ }
+
+ protected void handleMargins(UIDL uidl) {
+ final MarginInfo margins = new MarginInfo(uidl
+ .getIntAttribute("margins"));
+ // TODO build CSS detector to make margins configurable through css
+ marginTop = margins.hasTop() ? 15 : 0;
+ marginRight = margins.hasRight() ? 15 : 0;
+ marginBottom = margins.hasBottom() ? 15 : 0;
+ marginLeft = margins.hasLeft() ? 15 : 0;
+ }
+
+ private int detectSpacingSize() {
+ // TODO Auto-generated method stub
+ return 15;
+ }
+
+ public boolean hasChildComponent(Widget component) {
+ if (paintableToCellMap.containsKey(component)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public void replaceChildComponent(Widget oldComponent, Widget newComponent) {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void updateCaption(Paintable component, UIDL uidl) {
+ AbsoluteGridCell cell = (AbsoluteGridCell) paintableToCellMap
+ .get(component);
+ Caption c = cell.getCaption();
+ if (c == null) {
+ c = new Caption(component, client);
+ cell.setCaption(c);
+ }
+ c.updateCaption(uidl);
+ }
+
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client.ui.richtextarea;
+
+import com.google.gwt.user.client.ui.ChangeListener;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.FocusListener;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.RichTextArea;
+import com.google.gwt.user.client.ui.Widget;
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;
+import com.itmill.toolkit.terminal.gwt.client.Paintable;
+import com.itmill.toolkit.terminal.gwt.client.UIDL;
+import com.itmill.toolkit.terminal.gwt.client.ui.Field;
+
+/**
+ * This class represents a basic text input field with one row.
+ *
+ * @author IT Mill Ltd.
+ *
+ */
+public class IRichTextArea extends Composite implements Paintable, Field,
+ ChangeListener, FocusListener {
+
+ /**
+ * The input node CSS classname.
+ */
+ public static final String CLASSNAME = "i-richtextarea";
+
+ protected String id;
+
+ protected ApplicationConnection client;
+
+ private boolean immediate = false;
+
+ private RichTextArea rta = new RichTextArea();
+
+ private RichTextToolbar formatter = new RichTextToolbar(rta);
+
+ private HTML html = new HTML();
+
+ private final FlowPanel fp = new FlowPanel();
+
+ private boolean enabled = true;
+
+ public IRichTextArea() {
+ fp.add(formatter);
+
+ rta.setWidth("100%");
+ rta.addFocusListener(this);
+
+ fp.add(rta);
+
+ initWidget(fp);
+ setStyleName(CLASSNAME);
+
+ }
+
+ public void setEnabled(boolean enabled) {
+ if (this.enabled != enabled) {
+ rta.setEnabled(enabled);
+ if (enabled) {
+ fp.remove(html);
+ fp.add(rta);
+ } else {
+ html.setHTML(rta.getHTML());
+ fp.remove(rta);
+ fp.add(html);
+ }
+
+ this.enabled = enabled;
+ }
+ }
+
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+ this.client = client;
+ id = uidl.getId();
+
+ if (uidl.hasVariable("text")) {
+ rta.setHTML(uidl.getStringVariable("text"));
+ }
+ setEnabled(!uidl.getBooleanAttribute("disabled"));
+
+ if (client.updateComponent(this, uidl, true)) {
+ return;
+ }
+
+ immediate = uidl.getBooleanAttribute("immediate");
+
+ }
+
+ public void onChange(Widget sender) {
+ if (client != null && id != null) {
+ client.updateVariable(id, "text", rta.getText(), immediate);
+ }
+ }
+
+ public void onFocus(Widget sender) {
+
+ }
+
+ public void onLostFocus(Widget sender) {
+ final String html = rta.getHTML();
+ client.updateVariable(id, "text", html, immediate);
+
+ }
+
+}
--- /dev/null
+bold = Toggle Bold
+createLink = Create Link
+hr = Insert Horizontal Rule
+indent = Indent Right
+insertImage = Insert Image
+italic = Toggle Italic
+justifyCenter = Center
+justifyLeft = Left Justify
+justifyRight = Right Justify
+ol = Insert Ordered List
+outdent = Indent Left
+removeFormat = Remove Formatting
+removeLink = Remove Link
+strikeThrough = Toggle Strikethrough
+subscript = Toggle Subscript
+superscript = Toggle Superscript
+ul = Insert Unordered List
+underline = Toggle Underline
+color = Color
+black = Black
+white = White
+red = Red
+green = Green
+yellow = Yellow
+blue = Blue
+font = Font
+normal = Normal
+size = Size
+xxsmall = XX-Small
+xsmall = X-Small
+small = Small
+medium = Medium
+large = Large
+xlarge = X-Large
+xxlarge = XX-Large
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.itmill.toolkit.terminal.gwt.client.ui.richtextarea;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.i18n.client.Constants;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.AbstractImagePrototype;
+import com.google.gwt.user.client.ui.ChangeListener;
+import com.google.gwt.user.client.ui.ClickListener;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.ImageBundle;
+import com.google.gwt.user.client.ui.KeyboardListener;
+import com.google.gwt.user.client.ui.ListBox;
+import com.google.gwt.user.client.ui.PushButton;
+import com.google.gwt.user.client.ui.RichTextArea;
+import com.google.gwt.user.client.ui.ToggleButton;
+import com.google.gwt.user.client.ui.VerticalPanel;
+import com.google.gwt.user.client.ui.Widget;
+
+/**
+ * A sample toolbar for use with {@link RichTextArea}. It provides a simple UI
+ * for all rich text formatting, dynamically displayed only for the available
+ * functionality.
+ */
+public class RichTextToolbar extends Composite {
+
+ /**
+ * This {@link ImageBundle} is used for all the button icons. Using an image
+ * bundle allows all of these images to be packed into a single image, which
+ * saves a lot of HTTP requests, drastically improving startup time.
+ */
+ public interface Images extends ImageBundle {
+
+ /**
+ * @gwt.resource bold.gif
+ */
+ AbstractImagePrototype bold();
+
+ /**
+ * @gwt.resource createLink.gif
+ */
+ AbstractImagePrototype createLink();
+
+ /**
+ * @gwt.resource hr.gif
+ */
+ AbstractImagePrototype hr();
+
+ /**
+ * @gwt.resource indent.gif
+ */
+ AbstractImagePrototype indent();
+
+ /**
+ * @gwt.resource insertImage.gif
+ */
+ AbstractImagePrototype insertImage();
+
+ /**
+ * @gwt.resource italic.gif
+ */
+ AbstractImagePrototype italic();
+
+ /**
+ * @gwt.resource justifyCenter.gif
+ */
+ AbstractImagePrototype justifyCenter();
+
+ /**
+ * @gwt.resource justifyLeft.gif
+ */
+ AbstractImagePrototype justifyLeft();
+
+ /**
+ * @gwt.resource justifyRight.gif
+ */
+ AbstractImagePrototype justifyRight();
+
+ /**
+ * @gwt.resource ol.gif
+ */
+ AbstractImagePrototype ol();
+
+ /**
+ * @gwt.resource outdent.gif
+ */
+ AbstractImagePrototype outdent();
+
+ /**
+ * @gwt.resource removeFormat.gif
+ */
+ AbstractImagePrototype removeFormat();
+
+ /**
+ * @gwt.resource removeLink.gif
+ */
+ AbstractImagePrototype removeLink();
+
+ /**
+ * @gwt.resource strikeThrough.gif
+ */
+ AbstractImagePrototype strikeThrough();
+
+ /**
+ * @gwt.resource subscript.gif
+ */
+ AbstractImagePrototype subscript();
+
+ /**
+ * @gwt.resource superscript.gif
+ */
+ AbstractImagePrototype superscript();
+
+ /**
+ * @gwt.resource ul.gif
+ */
+ AbstractImagePrototype ul();
+
+ /**
+ * @gwt.resource underline.gif
+ */
+ AbstractImagePrototype underline();
+ }
+
+ /**
+ * This {@link Constants} interface is used to make the toolbar's strings
+ * internationalizable.
+ */
+ public interface Strings extends Constants {
+
+ String black();
+
+ String blue();
+
+ String bold();
+
+ String color();
+
+ String createLink();
+
+ String font();
+
+ String green();
+
+ String hr();
+
+ String indent();
+
+ String insertImage();
+
+ String italic();
+
+ String justifyCenter();
+
+ String justifyLeft();
+
+ String justifyRight();
+
+ String large();
+
+ String medium();
+
+ String normal();
+
+ String ol();
+
+ String outdent();
+
+ String red();
+
+ String removeFormat();
+
+ String removeLink();
+
+ String size();
+
+ String small();
+
+ String strikeThrough();
+
+ String subscript();
+
+ String superscript();
+
+ String ul();
+
+ String underline();
+
+ String white();
+
+ String xlarge();
+
+ String xsmall();
+
+ String xxlarge();
+
+ String xxsmall();
+
+ String yellow();
+ }
+
+ /**
+ * We use an inner EventListener class to avoid exposing event methods on
+ * the RichTextToolbar itself.
+ */
+ private class EventListener implements ClickListener, ChangeListener,
+ KeyboardListener {
+
+ public void onChange(Widget sender) {
+ if (sender == backColors) {
+ basic.setBackColor(backColors.getValue(backColors
+ .getSelectedIndex()));
+ backColors.setSelectedIndex(0);
+ } else if (sender == foreColors) {
+ basic.setForeColor(foreColors.getValue(foreColors
+ .getSelectedIndex()));
+ foreColors.setSelectedIndex(0);
+ } else if (sender == fonts) {
+ basic.setFontName(fonts.getValue(fonts.getSelectedIndex()));
+ fonts.setSelectedIndex(0);
+ } else if (sender == fontSizes) {
+ basic.setFontSize(fontSizesConstants[fontSizes
+ .getSelectedIndex() - 1]);
+ fontSizes.setSelectedIndex(0);
+ }
+ }
+
+ public void onClick(Widget sender) {
+ if (sender == bold) {
+ basic.toggleBold();
+ } else if (sender == italic) {
+ basic.toggleItalic();
+ } else if (sender == underline) {
+ basic.toggleUnderline();
+ } else if (sender == subscript) {
+ basic.toggleSubscript();
+ } else if (sender == superscript) {
+ basic.toggleSuperscript();
+ } else if (sender == strikethrough) {
+ extended.toggleStrikethrough();
+ } else if (sender == indent) {
+ extended.rightIndent();
+ } else if (sender == outdent) {
+ extended.leftIndent();
+ } else if (sender == justifyLeft) {
+ basic.setJustification(RichTextArea.Justification.LEFT);
+ } else if (sender == justifyCenter) {
+ basic.setJustification(RichTextArea.Justification.CENTER);
+ } else if (sender == justifyRight) {
+ basic.setJustification(RichTextArea.Justification.RIGHT);
+ } else if (sender == insertImage) {
+ final String url = Window.prompt("Enter an image URL:",
+ "http://");
+ if (url != null) {
+ extended.insertImage(url);
+ }
+ } else if (sender == createLink) {
+ final String url = Window
+ .prompt("Enter a link URL:", "http://");
+ if (url != null) {
+ extended.createLink(url);
+ }
+ } else if (sender == removeLink) {
+ extended.removeLink();
+ } else if (sender == hr) {
+ extended.insertHorizontalRule();
+ } else if (sender == ol) {
+ extended.insertOrderedList();
+ } else if (sender == ul) {
+ extended.insertUnorderedList();
+ } else if (sender == removeFormat) {
+ extended.removeFormat();
+ } else if (sender == richText) {
+ // We use the RichTextArea's onKeyUp event to update the toolbar
+ // status.
+ // This will catch any cases where the user moves the cursur
+ // using the
+ // keyboard, or uses one of the browser's built-in keyboard
+ // shortcuts.
+ updateStatus();
+ }
+ }
+
+ public void onKeyDown(Widget sender, char keyCode, int modifiers) {
+ }
+
+ public void onKeyPress(Widget sender, char keyCode, int modifiers) {
+ }
+
+ public void onKeyUp(Widget sender, char keyCode, int modifiers) {
+ if (sender == richText) {
+ // We use the RichTextArea's onKeyUp event to update the toolbar
+ // status.
+ // This will catch any cases where the user moves the cursur
+ // using the
+ // keyboard, or uses one of the browser's built-in keyboard
+ // shortcuts.
+ updateStatus();
+ }
+ }
+ }
+
+ private static final RichTextArea.FontSize[] fontSizesConstants = new RichTextArea.FontSize[] {
+ RichTextArea.FontSize.XX_SMALL, RichTextArea.FontSize.X_SMALL,
+ RichTextArea.FontSize.SMALL, RichTextArea.FontSize.MEDIUM,
+ RichTextArea.FontSize.LARGE, RichTextArea.FontSize.X_LARGE,
+ RichTextArea.FontSize.XX_LARGE };
+
+ private final Images images = (Images) GWT.create(Images.class);
+ private final Strings strings = (Strings) GWT.create(Strings.class);
+ private final EventListener listener = new EventListener();
+
+ private final RichTextArea richText;
+ private final RichTextArea.BasicFormatter basic;
+ private final RichTextArea.ExtendedFormatter extended;
+
+ private final VerticalPanel outer = new VerticalPanel();
+ private final HorizontalPanel topPanel = new HorizontalPanel();
+ private final HorizontalPanel bottomPanel = new HorizontalPanel();
+ private ToggleButton bold;
+ private ToggleButton italic;
+ private ToggleButton underline;
+ private ToggleButton subscript;
+ private ToggleButton superscript;
+ private ToggleButton strikethrough;
+ private PushButton indent;
+ private PushButton outdent;
+ private PushButton justifyLeft;
+ private PushButton justifyCenter;
+ private PushButton justifyRight;
+ private PushButton hr;
+ private PushButton ol;
+ private PushButton ul;
+ private PushButton insertImage;
+ private PushButton createLink;
+ private PushButton removeLink;
+ private PushButton removeFormat;
+
+ private ListBox backColors;
+ private ListBox foreColors;
+ private ListBox fonts;
+ private ListBox fontSizes;
+
+ /**
+ * Creates a new toolbar that drives the given rich text area.
+ *
+ * @param richText
+ * the rich text area to be controlled
+ */
+ public RichTextToolbar(RichTextArea richText) {
+ this.richText = richText;
+ basic = richText.getBasicFormatter();
+ extended = richText.getExtendedFormatter();
+
+ outer.add(topPanel);
+ outer.add(bottomPanel);
+ topPanel.setWidth("100%");
+ bottomPanel.setWidth("100%");
+
+ initWidget(outer);
+ setStyleName("gwt-RichTextToolbar");
+
+ if (basic != null) {
+ topPanel.add(bold = createToggleButton(images.bold(), strings
+ .bold()));
+ topPanel.add(italic = createToggleButton(images.italic(), strings
+ .italic()));
+ topPanel.add(underline = createToggleButton(images.underline(),
+ strings.underline()));
+ topPanel.add(subscript = createToggleButton(images.subscript(),
+ strings.subscript()));
+ topPanel.add(superscript = createToggleButton(images.superscript(),
+ strings.superscript()));
+ topPanel.add(justifyLeft = createPushButton(images.justifyLeft(),
+ strings.justifyLeft()));
+ topPanel.add(justifyCenter = createPushButton(images
+ .justifyCenter(), strings.justifyCenter()));
+ topPanel.add(justifyRight = createPushButton(images.justifyRight(),
+ strings.justifyRight()));
+ }
+
+ if (extended != null) {
+ topPanel.add(strikethrough = createToggleButton(images
+ .strikeThrough(), strings.strikeThrough()));
+ topPanel.add(indent = createPushButton(images.indent(), strings
+ .indent()));
+ topPanel.add(outdent = createPushButton(images.outdent(), strings
+ .outdent()));
+ topPanel.add(hr = createPushButton(images.hr(), strings.hr()));
+ topPanel.add(ol = createPushButton(images.ol(), strings.ol()));
+ topPanel.add(ul = createPushButton(images.ul(), strings.ul()));
+ topPanel.add(insertImage = createPushButton(images.insertImage(),
+ strings.insertImage()));
+ topPanel.add(createLink = createPushButton(images.createLink(),
+ strings.createLink()));
+ topPanel.add(removeLink = createPushButton(images.removeLink(),
+ strings.removeLink()));
+ topPanel.add(removeFormat = createPushButton(images.removeFormat(),
+ strings.removeFormat()));
+ }
+
+ if (basic != null) {
+ bottomPanel.add(backColors = createColorList("Background"));
+ bottomPanel.add(foreColors = createColorList("Foreground"));
+ bottomPanel.add(fonts = createFontList());
+ bottomPanel.add(fontSizes = createFontSizes());
+
+ // We only use these listeners for updating status, so don't hook
+ // them up
+ // unless at least basic editing is supported.
+ richText.addKeyboardListener(listener);
+ richText.addClickListener(listener);
+ }
+ }
+
+ private ListBox createColorList(String caption) {
+ final ListBox lb = new ListBox();
+ lb.addChangeListener(listener);
+ lb.setVisibleItemCount(1);
+
+ lb.addItem(caption);
+ lb.addItem(strings.white(), "white");
+ lb.addItem(strings.black(), "black");
+ lb.addItem(strings.red(), "red");
+ lb.addItem(strings.green(), "green");
+ lb.addItem(strings.yellow(), "yellow");
+ lb.addItem(strings.blue(), "blue");
+ return lb;
+ }
+
+ private ListBox createFontList() {
+ final ListBox lb = new ListBox();
+ lb.addChangeListener(listener);
+ lb.setVisibleItemCount(1);
+
+ lb.addItem(strings.font(), "");
+ lb.addItem(strings.normal(), "");
+ lb.addItem("Times New Roman", "Times New Roman");
+ lb.addItem("Arial", "Arial");
+ lb.addItem("Courier New", "Courier New");
+ lb.addItem("Georgia", "Georgia");
+ lb.addItem("Trebuchet", "Trebuchet");
+ lb.addItem("Verdana", "Verdana");
+ return lb;
+ }
+
+ private ListBox createFontSizes() {
+ final ListBox lb = new ListBox();
+ lb.addChangeListener(listener);
+ lb.setVisibleItemCount(1);
+
+ lb.addItem(strings.size());
+ lb.addItem(strings.xxsmall());
+ lb.addItem(strings.xsmall());
+ lb.addItem(strings.small());
+ lb.addItem(strings.medium());
+ lb.addItem(strings.large());
+ lb.addItem(strings.xlarge());
+ lb.addItem(strings.xxlarge());
+ return lb;
+ }
+
+ private PushButton createPushButton(AbstractImagePrototype img, String tip) {
+ final PushButton pb = new PushButton(img.createImage());
+ pb.addClickListener(listener);
+ pb.setTitle(tip);
+ return pb;
+ }
+
+ private ToggleButton createToggleButton(AbstractImagePrototype img,
+ String tip) {
+ final ToggleButton tb = new ToggleButton(img.createImage());
+ tb.addClickListener(listener);
+ tb.setTitle(tip);
+ return tb;
+ }
+
+ /**
+ * Updates the status of all the stateful buttons.
+ */
+ private void updateStatus() {
+ if (basic != null) {
+ bold.setDown(basic.isBold());
+ italic.setDown(basic.isItalic());
+ underline.setDown(basic.isUnderlined());
+ subscript.setDown(basic.isSubscript());
+ superscript.setDown(basic.isSuperscript());
+ }
+
+ if (extended != null) {
+ strikethrough.setDown(extended.isStrikethrough());
+ }
+ }
+}
--- /dev/null
+package com.itmill.toolkit.terminal.gwt.server;\r
+\r
+import java.io.IOException;\r
+import java.io.PrintWriter;\r
+\r
+import javax.portlet.ActionRequest;\r
+import javax.portlet.ActionResponse;\r
+import javax.portlet.Portlet;\r
+import javax.portlet.PortletConfig;\r
+import javax.portlet.PortletException;\r
+import javax.portlet.PortletRequestDispatcher;\r
+import javax.portlet.PortletSession;\r
+import javax.portlet.RenderRequest;\r
+import javax.portlet.RenderResponse;\r
+\r
+import com.itmill.toolkit.Application;\r
+\r
+public class ApplicationPortlet implements Portlet {\r
+ // The application to show\r
+ protected String app = null;\r
+ // some applications might require forced height (and, more seldom, width)\r
+ protected String style = null; // e.g "height:500px;"\r
+ protected String widgetset = null;\r
+\r
+ public void destroy() {\r
+\r
+ }\r
+\r
+ public void init(PortletConfig config) throws PortletException {\r
+ app = config.getInitParameter("application");\r
+ if (app == null) {\r
+ app = "PortletDemo";\r
+ }\r
+ style = config.getInitParameter("style");\r
+ widgetset = config.getInitParameter("widgetset");\r
+ }\r
+\r
+ public void processAction(ActionRequest request, ActionResponse response)\r
+ throws PortletException, IOException {\r
+ PortletApplicationContext.dispatchRequest(this, request, response);\r
+ }\r
+\r
+ public void render(RenderRequest request, RenderResponse response)\r
+ throws PortletException, IOException {\r
+\r
+ // display the IT Mill Toolkit application\r
+ writeAjaxWindow(request, response);\r
+ }\r
+\r
+ protected void writeAjaxWindow(RenderRequest request,\r
+ RenderResponse response) throws IOException {\r
+\r
+ response.setContentType("text/html");\r
+ if (app != null) {\r
+ PortletSession sess = request.getPortletSession();\r
+ PortletApplicationContext ctx = PortletApplicationContext\r
+ .getApplicationContext(sess);\r
+\r
+ PortletRequestDispatcher dispatcher = sess.getPortletContext()\r
+ .getRequestDispatcher("/" + app);\r
+\r
+ try {\r
+ request.setAttribute(ApplicationServlet.REQUEST_FRAGMENT,\r
+ "true");\r
+ if (widgetset != null) {\r
+ request.setAttribute(ApplicationServlet.REQUEST_WIDGETSET,\r
+ widgetset);\r
+ }\r
+ if (style != null) {\r
+ request.setAttribute(ApplicationServlet.REQUEST_APPSTYLE,\r
+ style);\r
+ }\r
+ dispatcher.include(request, response);\r
+\r
+ } catch (PortletException e) {\r
+ PrintWriter out = response.getWriter();\r
+ out.print("<h1>Servlet include failed!</h1>");\r
+ out.print("<div>" + e + "</div>");\r
+ ctx.setPortletApplication(this, null);\r
+ return;\r
+ }\r
+\r
+ Application app = (Application) request\r
+ .getAttribute(Application.class.getName());\r
+ ctx.setPortletApplication(this, app);\r
+ ctx.firePortletRenderRequest(this, request, response);\r
+\r
+ }\r
+ }\r
+\r
+}\r
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.server;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.xml.sax.SAXException;
+
+import com.itmill.toolkit.Application;
+import com.itmill.toolkit.Application.SystemMessages;
+import com.itmill.toolkit.external.org.apache.commons.fileupload.servlet.ServletFileUpload;
+import com.itmill.toolkit.service.FileTypeResolver;
+import com.itmill.toolkit.terminal.DownloadStream;
+import com.itmill.toolkit.terminal.ParameterHandler;
+import com.itmill.toolkit.terminal.ThemeResource;
+import com.itmill.toolkit.terminal.URIHandler;
+import com.itmill.toolkit.ui.Window;
+
+/**
+ * This servlet connects IT Mill Toolkit Application to Web.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 5.0
+ */
+
+public class ApplicationServlet extends HttpServlet {
+
+ private static final long serialVersionUID = -4937882979845826574L;
+
+ /**
+ * Version number of this release. For example "5.0.0".
+ */
+ public static final String VERSION;
+
+ /**
+ * Major version number. For example 5 in 5.1.0.
+ */
+ public static final int VERSION_MAJOR;
+
+ /**
+ * Minor version number. For example 1 in 5.1.0.
+ */
+ public static final int VERSION_MINOR;
+
+ /**
+ * Builds number. For example 0-custom_tag in 5.0.0-custom_tag.
+ */
+ public static final String VERSION_BUILD;
+
+ /* Initialize version numbers from string replaced by build-script. */
+ static {
+ if ("@VERSION@".equals("@" + "VERSION" + "@")) {
+ VERSION = "5.9.9-INTERNAL-NONVERSIONED-DEBUG-BUILD";
+ } else {
+ VERSION = "@VERSION@";
+ }
+ final String[] digits = VERSION.split("\\.");
+ VERSION_MAJOR = Integer.parseInt(digits[0]);
+ VERSION_MINOR = Integer.parseInt(digits[1]);
+ VERSION_BUILD = digits[2];
+ }
+
+ /**
+ * If the attribute is present in the request, a html fragment will be
+ * written instead of a whole page.
+ */
+ public static final String REQUEST_FRAGMENT = ApplicationServlet.class
+ .getName()
+ + ".fragment";
+ /**
+ * This request attribute forces widgetset used; e.g for portlets that can
+ * not have different widgetsets.
+ */
+ public static final String REQUEST_WIDGETSET = ApplicationServlet.class
+ .getName()
+ + ".widgetset";
+ /**
+ * This request attribute is used to add styles to the main element. E.g
+ * "height:500px" generates a style="height:500px" to the main element,
+ * useful from some embedding situations (e.g portlet include.)
+ */
+ public static final String REQUEST_APPSTYLE = ApplicationServlet.class
+ .getName()
+ + ".style";
+
+ // Configurable parameter names
+ private static final String PARAMETER_DEBUG = "Debug";
+
+ private static final String PARAMETER_ITMILL_RESOURCES = "Resources";
+
+ private static final int DEFAULT_BUFFER_SIZE = 32 * 1024;
+
+ private static final int MAX_BUFFER_SIZE = 64 * 1024;
+
+ // TODO This is session specific not servlet wide data. No need to store
+ // this here, move it to Session from where it can be queried when required
+ protected static HashMap applicationToAjaxAppMgrMap = new HashMap();
+
+ private static final String RESOURCE_URI = "/RES/";
+
+ private static final String AJAX_UIDL_URI = "/UIDL";
+
+ static final String THEME_DIRECTORY_PATH = "ITMILL/themes/";
+
+ private static final int DEFAULT_THEME_CACHETIME = 1000 * 60 * 60 * 24;
+
+ static final String WIDGETSET_DIRECTORY_PATH = "ITMILL/widgetsets/";
+
+ // Name of the default widget set, used if not specified in web.xml
+ private static final String DEFAULT_WIDGETSET = "com.itmill.toolkit.terminal.gwt.DefaultWidgetSet";
+
+ // Widget set parameter name
+ private static final String PARAMETER_WIDGETSET = "widgetset";
+
+ // Private fields
+ private Class applicationClass;
+
+ private Properties applicationProperties;
+
+ private String resourcePath = null;
+
+ private String debugMode = "";
+
+ // Is this servlet application runner
+ private boolean isApplicationRunnerServlet = false;
+
+ // If servlet is application runner, store request's classname
+ private String applicationRunnerClassname = null;
+
+ private ClassLoader classLoader;
+
+ private boolean testingToolsActive = false;
+
+ private String testingToolsServerUri = null;
+
+ /**
+ * Called by the servlet container to indicate to a servlet that the servlet
+ * is being placed into service.
+ *
+ * @param servletConfig
+ * the object containing the servlet's configuration and
+ * initialization parameters
+ * @throws javax.servlet.ServletException
+ * if an exception has occurred that interferes with the
+ * servlet's normal operation.
+ */
+ public void init(javax.servlet.ServletConfig servletConfig)
+ throws javax.servlet.ServletException {
+ super.init(servletConfig);
+
+ // Get applicationRunner
+ final String applicationRunner = servletConfig
+ .getInitParameter("applicationRunner");
+ if (applicationRunner != null) {
+ if ("true".equals(applicationRunner)) {
+ isApplicationRunnerServlet = true;
+ } else if ("false".equals(applicationRunner)) {
+ isApplicationRunnerServlet = false;
+ } else {
+ throw new ServletException(
+ "If applicationRunner parameter is given for an application, it must be 'true' or 'false'");
+ }
+ }
+
+ // Stores the application parameters into Properties object
+ applicationProperties = new Properties();
+ for (final Enumeration e = servletConfig.getInitParameterNames(); e
+ .hasMoreElements();) {
+ final String name = (String) e.nextElement();
+ applicationProperties.setProperty(name, servletConfig
+ .getInitParameter(name));
+ }
+
+ // Overrides with server.xml parameters
+ final ServletContext context = servletConfig.getServletContext();
+ for (final Enumeration e = context.getInitParameterNames(); e
+ .hasMoreElements();) {
+ final String name = (String) e.nextElement();
+ applicationProperties.setProperty(name, context
+ .getInitParameter(name));
+ }
+
+ // Gets the debug window parameter
+ final String debug = getApplicationOrSystemProperty(PARAMETER_DEBUG, "")
+ .toLowerCase();
+
+ // Enables application specific debug
+ if (!"".equals(debug) && !"true".equals(debug)
+ && !"false".equals(debug)) {
+ throw new ServletException(
+ "If debug parameter is given for an application, it must be 'true' or 'false'");
+ }
+ debugMode = debug;
+
+ // Gets Testing Tools parameters if feature is activated
+ if (getApplicationOrSystemProperty("testingToolsActive", "false")
+ .equals("true")) {
+ testingToolsActive = true;
+ testingToolsServerUri = getApplicationOrSystemProperty(
+ "testingToolsServerUri", null);
+ }
+
+ // Gets custom class loader
+ final String classLoaderName = getApplicationOrSystemProperty(
+ "ClassLoader", null);
+ ClassLoader classLoader;
+ if (classLoaderName == null) {
+ classLoader = getClass().getClassLoader();
+ } else {
+ try {
+ final Class classLoaderClass = getClass().getClassLoader()
+ .loadClass(classLoaderName);
+ final Constructor c = classLoaderClass
+ .getConstructor(new Class[] { ClassLoader.class });
+ classLoader = (ClassLoader) c
+ .newInstance(new Object[] { getClass().getClassLoader() });
+ } catch (final Exception e) {
+ System.err.println("Could not find specified class loader: "
+ + classLoaderName);
+ throw new ServletException(e);
+ }
+ }
+ this.classLoader = classLoader;
+
+ // Loads the application class using the same class loader
+ // as the servlet itself
+ if (!isApplicationRunnerServlet) {
+ // Gets the application class name
+ final String applicationClassName = servletConfig
+ .getInitParameter("application");
+ if (applicationClassName == null) {
+ throw new ServletException(
+ "Application not specified in servlet parameters");
+ }
+ try {
+ applicationClass = classLoader.loadClass(applicationClassName);
+ } catch (final ClassNotFoundException e) {
+ throw new ServletException("Failed to load application class: "
+ + applicationClassName);
+ }
+ } else {
+ // This servlet is in application runner mode, it uses classloader
+ // later to create Applications based on URL
+ }
+
+ }
+
+ /**
+ * Gets an application or system property value.
+ *
+ * @param parameterName
+ * the Name or the parameter.
+ * @param defaultValue
+ * the Default to be used.
+ * @return String value or default if not found
+ */
+ private String getApplicationOrSystemProperty(String parameterName,
+ String defaultValue) {
+
+ // Try application properties
+ String val = applicationProperties.getProperty(parameterName);
+ if (val != null) {
+ return val;
+ }
+
+ // Try lowercased application properties for backward compability with
+ // 3.0.2 and earlier
+ val = applicationProperties.getProperty(parameterName.toLowerCase());
+ if (val != null) {
+ return val;
+ }
+
+ // Try system properties
+ String pkgName;
+ final Package pkg = getClass().getPackage();
+ if (pkg != null) {
+ pkgName = pkg.getName();
+ } else {
+ final String className = getClass().getName();
+ pkgName = new String(className.toCharArray(), 0, className
+ .lastIndexOf('.'));
+ }
+ val = System.getProperty(pkgName + "." + parameterName);
+ if (val != null) {
+ return val;
+ }
+
+ // Try lowercased system properties
+ val = System.getProperty(pkgName + "." + parameterName.toLowerCase());
+ if (val != null) {
+ return val;
+ }
+
+ return defaultValue;
+ }
+
+ /**
+ * Receives standard HTTP requests from the public service method and
+ * dispatches them.
+ *
+ * @param request
+ * the object that contains the request the client made of
+ * the servlet.
+ * @param response
+ * the object that contains the response the servlet returns
+ * to the client.
+ * @throws ServletException
+ * if an input or output error occurs while the servlet is
+ * handling the TRACE request.
+ * @throws IOException
+ * if the request for the TRACE cannot be handled.
+ */
+ protected void service(HttpServletRequest request,
+ HttpServletResponse response) throws ServletException, IOException {
+
+ // check if we should serve static files (widgetsets, themes)
+ if ((request.getPathInfo() != null)
+ && (request.getPathInfo().length() > 10)) {
+ if ((request.getContextPath() != null)
+ && (request.getRequestURI().startsWith("/ITMILL/"))) {
+ serveStaticResourcesInITMILL(request.getRequestURI(), response);
+ return;
+ } else if (request.getRequestURI().startsWith(
+ request.getContextPath() + "/ITMILL/")) {
+ serveStaticResourcesInITMILL(request.getRequestURI().substring(
+ request.getContextPath().length()), response);
+ return;
+ }
+ }
+
+ Application application = null;
+ boolean UIDLrequest = false;
+ try {
+ // handle file upload if multipart request
+ if (ServletFileUpload.isMultipartContent(request)) {
+ application = getExistingApplication(request, response);
+ if (application == null) {
+ throw new SessionExpired();
+ }
+ // Invokes context transaction listeners
+ // note: endTransaction is called on finalize below
+ ((WebApplicationContext) application.getContext())
+ .startTransaction(application, request);
+ getApplicationManager(application).handleFileUpload(request,
+ response);
+ return;
+ }
+
+ // Update browser details
+ final WebBrowser browser = WebApplicationContext
+ .getApplicationContext(request.getSession()).getBrowser();
+ browser.updateBrowserProperties(request);
+ // TODO Add screen height and width to the GWT client
+
+ // Handles AJAX UIDL requests
+ if (request.getPathInfo() != null) {
+
+ String compare = AJAX_UIDL_URI;
+ if (isApplicationRunnerServlet) {
+ final String[] URIparts = getApplicationRunnerURIs(request);
+ applicationRunnerClassname = URIparts[4];
+ compare = "/" + applicationRunnerClassname + AJAX_UIDL_URI;
+ }
+
+ if (request.getPathInfo().startsWith(compare + "/")
+ || request.getPathInfo().endsWith(compare)) {
+ UIDLrequest = true;
+ application = getExistingApplication(request, response);
+ if (application == null) {
+ // No existing applications found
+ final String repaintAll = request
+ .getParameter("repaintAll");
+ if ((repaintAll != null) && (repaintAll.equals("1"))) {
+ // UIDL request contains valid repaintAll=1 event,
+ // probably user wants to initiate new application
+ // through custom index.html without writeAjaxPage
+ application = getNewApplication(request, response);
+ } else {
+ // UIDL request refers to non-existing application
+ throw new SessionExpired();
+ }
+ }
+
+ // Invokes context transaction listeners
+ // note: endTransaction is called on finalize below
+ ((WebApplicationContext) application.getContext())
+ .startTransaction(application, request);
+
+ // Handle UIDL request
+ getApplicationManager(application).handleUidlRequest(
+ request, response, this);
+ return;
+ }
+ }
+
+ // Get existing application
+ application = getExistingApplication(request, response);
+ if (application == null
+ || request.getParameter("restartApplication") != null
+ || request.getParameter("closeApplication") != null) {
+ if (application != null) {
+ application.close();
+ final HttpSession session = request.getSession(false);
+ if (session != null) {
+ ApplicationServlet.applicationToAjaxAppMgrMap
+ .remove(application);
+ WebApplicationContext.getApplicationContext(session)
+ .removeApplication(application);
+ }
+ }
+ if (request.getParameter("closeApplication") != null) {
+ return;
+ }
+ // Not found, creating new application
+ application = getNewApplication(request, response);
+ }
+
+ // Invokes context transaction listeners
+ // note: endTransaction is called on finalize below
+ ((WebApplicationContext) application.getContext())
+ .startTransaction(application, request);
+
+ // Removes application if it has stopped
+ if (!application.isRunning()) {
+ endApplication(request, response, application);
+ return;
+ }
+
+ // Finds the window within the application
+ Window window = null;
+ window = getApplicationWindow(request, application);
+
+ // Handle parameters
+ final Map parameters = request.getParameterMap();
+ if (window != null && parameters != null) {
+ window.handleParameters(parameters);
+ }
+
+ // Is this a download request from application
+ DownloadStream download = null;
+
+ // Handles the URI if the application is still running
+ download = handleURI(application, request, response);
+
+ // If this is not a download request
+ if (download == null) {
+
+ // Sets terminal type for the window, if not already set
+ if (window.getTerminal() == null) {
+ window.setTerminal(browser);
+ }
+
+ // Finds theme name
+ String themeName = window.getTheme();
+ if (request.getParameter("theme") != null) {
+ themeName = request.getParameter("theme");
+ }
+
+ if (themeName == null) {
+ themeName = "default";
+ }
+
+ // Handles resource requests
+ if (handleResourceRequest(request, response, themeName)) {
+ return;
+ }
+
+ // Send initial AJAX page that kickstarts Toolkit application
+ writeAjaxPage(request, response, window, themeName, application);
+
+ } else {
+ // Client downloads an resource
+ handleDownload(download, request, response);
+ }
+
+ } catch (final SessionExpired e) {
+ // Session has expired, notify user
+ Application.SystemMessages ci = getSystemMessages();
+ if (!UIDLrequest) {
+ // 'plain' http req - e.g. browser reload;
+ // just go ahead redirect the browser
+ response.sendRedirect(ci.getSessionExpiredURL());
+ } else {
+ // send uidl redirect
+ criticalNotification(request, response, ci
+ .getSessionExpiredCaption(), ci
+ .getSessionExpiredMessage(), ci.getSessionExpiredURL());
+ }
+
+ } catch (final Throwable e) {
+ e.printStackTrace();
+ // if this was an UIDL request, response UIDL back to client
+ if (UIDLrequest) {
+ Application.SystemMessages ci = getSystemMessages();
+ criticalNotification(request, response, ci
+ .getInternalErrorCaption(), ci
+ .getInternalErrorMessage(), ci.getInternalErrorURL());
+ } else {
+ // Re-throw other exceptions
+ throw new ServletException(e);
+ }
+ } finally {
+ // Notifies transaction end
+ if (application != null) {
+ ((WebApplicationContext) application.getContext())
+ .endTransaction(application, request);
+ }
+ }
+ }
+
+ /** Get system messages from the current application class */
+ private SystemMessages getSystemMessages() {
+ try {
+ Class appCls = applicationClass;
+ if (isApplicationRunnerServlet) {
+ appCls = getClass().getClassLoader().loadClass(
+ applicationRunnerClassname);
+ }
+ Method m = appCls.getMethod("getSystemMessages", null);
+ return (Application.SystemMessages) m.invoke(null, null);
+ } catch (ClassNotFoundException e) {
+ // This should never happen
+ e.printStackTrace();
+ } catch (SecurityException e) {
+ e.printStackTrace();
+ System.out
+ .print("Error: getSystemMessage() should be static public");
+ } catch (NoSuchMethodException e) {
+ // This is completely ok and should be silently ignored
+ } catch (IllegalArgumentException e) {
+ // This should never happen
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ System.out
+ .print("Error: getSystemMessage() should be static public");
+ } catch (InvocationTargetException e) {
+ // This should never happen
+ e.printStackTrace();
+ }
+ return Application.getSystemMessages();
+ }
+
+ /**
+ * Serve resources in ITMILL directory if requested.
+ *
+ * @param request
+ * @param response
+ * @throws IOException
+ */
+ private void serveStaticResourcesInITMILL(String filename,
+ HttpServletResponse response) throws IOException {
+
+ final ServletContext sc = getServletContext();
+ InputStream is = sc.getResourceAsStream(filename);
+ if (is == null) {
+ // try if requested file is found from classloader
+ try {
+ // strip leading "/" otherwise stream from JAR wont work
+ filename = filename.substring(1);
+ is = classLoader.getResourceAsStream(filename);
+ } catch (final Exception e) {
+ e.printStackTrace();
+ }
+ if (is == null) {
+ // cannot serve requested file
+ System.err
+ .println("Requested resource ["
+ + filename
+ + "] not found from filesystem or through class loader."
+ + " Add widgetset and/or theme JAR to your classpath or add files to WebContent/ITMILL folder.");
+ response.setStatus(404);
+ return;
+ }
+ }
+ final String mimetype = sc.getMimeType(filename);
+ if (mimetype != null) {
+ response.setContentType(mimetype);
+ }
+ final OutputStream os = response.getOutputStream();
+ final byte buffer[] = new byte[20000];
+ int bytes;
+ while ((bytes = is.read(buffer)) >= 0) {
+ os.write(buffer, 0, bytes);
+ }
+ }
+
+ /**
+ * Send notification to client's application. Used to notify client of
+ * critical errors and session expiration due to long inactivity. Server has
+ * no knowledge of what application client refers to.
+ *
+ * @param request
+ * the HTTP request instance.
+ * @param response
+ * the HTTP response to write to.
+ * @param caption
+ * for the notification
+ * @param message
+ * for the notification
+ * @param url
+ * url to load after message, null for current page
+ * @throws IOException
+ * if the writing failed due to input/output error.
+ */
+ void criticalNotification(HttpServletRequest request,
+ HttpServletResponse response, String caption, String message,
+ String url) throws IOException {
+
+ // clients JS app is still running, but server application either
+ // no longer exists or it might fail to perform reasonably.
+ // send a notification to client's application and link how
+ // to "restart" application.
+
+ if (caption != null) {
+ caption = "\"" + caption + "\"";
+ }
+ if (message != null) {
+ message = "\"" + message + "\"";
+ }
+ if (url != null) {
+ url = "\"" + url + "\"";
+ }
+
+ // Set the response type
+ response.setContentType("application/json; charset=UTF-8");
+ final ServletOutputStream out = response.getOutputStream();
+ final PrintWriter outWriter = new PrintWriter(new BufferedWriter(
+ new OutputStreamWriter(out, "UTF-8")));
+ outWriter.print("for(;;);[{\"changes\":[], \"meta\" : {"
+ + "\"appError\": {" + "\"caption\":" + caption + ","
+ + "\"message\" : " + message + "," + "\"url\" : " + url
+ + "}}, \"resources\": {}, \"locales\":[]}]");
+ outWriter.flush();
+ outWriter.close();
+ out.flush();
+ }
+
+ /**
+ * Resolve application URL and widgetset URL. Widgetset is not application
+ * specific.
+ *
+ * @param request
+ * @return string array consisting of application url first and then
+ * widgetset url.
+ */
+ private String[] getAppAndWidgetUrl(HttpServletRequest request) {
+ // don't use server and port in uri. It may cause problems with some
+ // virtual server configurations which lose the server name
+ String appUrl = null;
+ String widgetsetUrl = null;
+ if (isApplicationRunnerServlet) {
+ final String[] URIparts = getApplicationRunnerURIs(request);
+ widgetsetUrl = URIparts[0];
+ if (widgetsetUrl.equals("/")) {
+ widgetsetUrl = "";
+ }
+ appUrl = URIparts[1];
+ } else {
+ String[] urlParts;
+ try {
+ urlParts = getApplicationUrl(request).toString().split("\\/");
+ appUrl = "";
+ widgetsetUrl = "";
+ // if context is specified add it to widgetsetUrl
+ String ctxPath = request.getContextPath();
+ if (ctxPath.length() == 0
+ && request
+ .getAttribute("javax.servlet.include.context_path") != null) {
+ // include request (e.g portlet), get contex path from
+ // attribute
+ ctxPath = (String) request
+ .getAttribute("javax.servlet.include.context_path");
+ }
+ if (urlParts.length > 3
+ && urlParts[3].equals(ctxPath.replaceAll("\\/", ""))) {
+ widgetsetUrl += "/" + urlParts[3];
+ }
+ for (int i = 3; i < urlParts.length; i++) {
+ appUrl += "/" + urlParts[i];
+ }
+ if (appUrl.endsWith("/")) {
+ appUrl = appUrl.substring(0, appUrl.length() - 1);
+ }
+ } catch (final MalformedURLException e) {
+ e.printStackTrace();
+ }
+
+ }
+ return new String[] { appUrl, widgetsetUrl };
+ }
+
+ /**
+ *
+ * @param request
+ * the HTTP request.
+ * @param response
+ * the HTTP response to write to.
+ * @param out
+ * @param unhandledParameters
+ * @param window
+ * @param terminalType
+ * @param theme
+ * @throws IOException
+ * if the writing failed due to input/output error.
+ * @throws MalformedURLException
+ * if the application is denied access the persistent data
+ * store represented by the given URL.
+ */
+ private void writeAjaxPage(HttpServletRequest request,
+ HttpServletResponse response, Window window, String themeName,
+ Application application) throws IOException, MalformedURLException {
+
+ // e.g portlets only want a html fragment
+ boolean fragment = (request.getAttribute(REQUEST_FRAGMENT) != null);
+ if (fragment) {
+ request.setAttribute(Application.class.getName(), application);
+ }
+
+ final BufferedWriter page = new BufferedWriter(new OutputStreamWriter(
+ response.getOutputStream()));
+ final String pathInfo = request.getPathInfo() == null ? "/" : request
+ .getPathInfo();
+ String title = ((window == null || window.getCaption() == null) ? "IT Mill Toolkit 5"
+ : window.getCaption());
+
+ String widgetset = null;
+ // request widgetset takes precedence (e.g portlet include)
+ Object reqParam = request.getAttribute(REQUEST_WIDGETSET);
+ try {
+ widgetset = (String) reqParam;
+ } catch (Exception e) {
+ System.err.println("Warning: request param " + REQUEST_WIDGETSET
+ + " could not be used (not a String?)" + e);
+ }
+ if (widgetset == null) {
+ widgetset = applicationProperties.getProperty(PARAMETER_WIDGETSET);
+ }
+ if (widgetset == null) {
+ widgetset = DEFAULT_WIDGETSET;
+ }
+ final String[] urls = getAppAndWidgetUrl(request);
+ final String appUrl = urls[0];
+ final String widgetsetUrl = urls[1];
+
+ final String staticFilePath = getApplicationOrSystemProperty(
+ PARAMETER_ITMILL_RESOURCES, widgetsetUrl);
+
+ // Default theme does not use theme URI
+ String themeUri = null;
+ if (themeName != null) {
+ // Using custom theme
+ themeUri = staticFilePath + "/" + THEME_DIRECTORY_PATH + themeName;
+ }
+
+ boolean testingApplication = testingToolsActive
+ && request.getParameter("TT") != null;
+
+ if (!fragment) {
+ // Window renders are not cacheable
+ response.setCharacterEncoding("utf-8");
+ response.setHeader("Cache-Control", "no-cache");
+ response.setHeader("Pragma", "no-cache");
+ response.setDateHeader("Expires", 0);
+ response.setContentType("text/html");
+
+ // write html header
+ page.write("<!DOCTYPE html PUBLIC \"-//W3C//DTD "
+ + "XHTML 1.0 Transitional//EN\" "
+ + "\"http://www.w3.org/TR/xhtml1/"
+ + "DTD/xhtml1-transitional.dtd\">\n");
+
+ page.write("<html xmlns=\"http://www.w3.org/1999/xhtml\""
+ + ">\n<head>\n");
+ page.write("<style type=\"text/css\">"
+ + "html, body {height:100%;}</style>");
+ page.write("<title>" + title + "</title>");
+
+ if (testingApplication) {
+ // TT script needs to be in head as it needs to be the first
+ // to hook capturing event listeners
+ writeTestingToolsScripts(page, request);
+ }
+
+ page
+ .write("\n</head>\n<body scroll=\"auto\" class=\"i-generated-body\">\n");
+ }
+
+ String appId = appUrl;
+ if ("".equals(appUrl)) {
+ appId = "ROOT";
+ }
+ appId = appId.replaceAll("[^a-zA-Z0-9]", "");
+
+ if (isGecko17(request)) {
+ // special start page for gecko 1.7 versions. Firefox 1.0 is not
+ // supported, but the hack is make it possible to use linux and
+ // hosted mode browser for debugging. Note that due this hack,
+ // debugging gwt code in portals with linux will be problematic if
+ // there are multiple toolkit portlets visible at the same time.
+ // TODO remove this when hosted mode on linux gets newer gecko
+
+ page.write("<iframe id=\"__gwt_historyFrame\" "
+ + "style=\"width:0;height:0;border:0;overflow:"
+ + "hidden\" src=\"javascript:false\"></iframe>\n");
+ page.write("<script language='javascript' src='" + staticFilePath
+ + "/" + WIDGETSET_DIRECTORY_PATH + widgetset + "/"
+ + widgetset + ".nocache.js'></script>\n");
+ page.write("<script type=\"text/javascript\">\n");
+ page.write("//<![CDATA[\n");
+ page.write("if(!itmill || !itmill.toolkitConfigurations) {\n "
+ + "if(!itmill) { var itmill = {}} \n"
+ + "itmill.toolkitConfigurations = {};\n"
+ + "itmill.themesLoaded = {}};\n");
+
+ page.write("itmill.toolkitConfigurations[\"" + appId + "\"] = {");
+ page.write("appUri:'" + appUrl + "', ");
+ page.write("pathInfo: '" + pathInfo + "', ");
+ page.write("themeUri:");
+ page.write(themeUri != null ? "'" + themeUri + "'" : "null");
+ if (testingApplication) {
+ page.write(", versionInfo : {toolkitVersion:\"");
+ page.write(VERSION);
+ page.write("\",applicationVersion:\"");
+ page.write(application.getVersion());
+ page.write("\"}");
+ }
+ page.write("};\n//]]>\n</script>\n");
+
+ if (themeName != null) {
+ // Custom theme's stylesheet, load only once, in different
+ // script
+ // tag to be dominate styles injected by widget
+ // set
+ page.write("<script type=\"text/javascript\">\n");
+ page.write("//<![CDATA[\n");
+ page.write("if(!itmill.themesLoaded['" + themeName + "']) {\n");
+ page
+ .write("var stylesheet = document.createElement('link');\n");
+ page.write("stylesheet.setAttribute('rel', 'stylesheet');\n");
+ page.write("stylesheet.setAttribute('type', 'text/css');\n");
+ page.write("stylesheet.setAttribute('href', '" + themeUri
+ + "/styles.css');\n");
+ page
+ .write("document.getElementsByTagName('head')[0].appendChild(stylesheet);\n");
+ page.write("itmill.themesLoaded['" + themeName
+ + "'] = true;\n}\n");
+ page.write("//]]>\n</script>\n");
+ }
+
+ } else {
+ page.write("<script type=\"text/javascript\">\n");
+ page.write("//<![CDATA[\n");
+ page.write("if(!itmill || !itmill.toolkitConfigurations) {\n "
+ + "if(!itmill) { var itmill = {}} \n"
+ + "itmill.toolkitConfigurations = {};\n"
+ + "itmill.themesLoaded = {};\n");
+ page.write("document.write('<iframe id=\"__gwt_historyFrame\" "
+ + "style=\"width:0;height:0;border:0;overflow:"
+ + "hidden\" src=\"javascript:false\"></iframe>');\n");
+ page.write("document.write(\"<script language='javascript' src='"
+ + staticFilePath + "/" + WIDGETSET_DIRECTORY_PATH
+ + widgetset + "/" + widgetset
+ + ".nocache.js'><\\/script>\");\n}\n");
+
+ page.write("itmill.toolkitConfigurations[\"" + appId + "\"] = {");
+ page.write("appUri:'" + appUrl + "', ");
+ page.write("pathInfo: '" + pathInfo + "', ");
+ page.write("themeUri:");
+ page.write(themeUri != null ? "'" + themeUri + "'" : "null");
+ if (testingApplication) {
+ page.write(", versionInfo : {toolkitVersion:\"");
+ page.write(VERSION);
+ page.write("\",applicationVersion:\"");
+ page.write(application.getVersion());
+ page.write("\"}");
+ }
+ page.write("};\n//]]>\n</script>\n");
+
+ if (themeName != null) {
+ // Custom theme's stylesheet, load only once, in different
+ // script
+ // tag to be dominate styles injected by widget
+ // set
+ page.write("<script type=\"text/javascript\">\n");
+ page.write("//<![CDATA[\n");
+ page.write("if(!itmill.themesLoaded['" + themeName + "']) {\n");
+ page
+ .write("var stylesheet = document.createElement('link');\n");
+ page.write("stylesheet.setAttribute('rel', 'stylesheet');\n");
+ page.write("stylesheet.setAttribute('type', 'text/css');\n");
+ page.write("stylesheet.setAttribute('href', '" + themeUri
+ + "/styles.css');\n");
+ page
+ .write("document.getElementsByTagName('head')[0].appendChild(stylesheet);\n");
+ page.write("itmill.themesLoaded['" + themeName
+ + "'] = true;\n}\n");
+ page.write("//]]>\n</script>\n");
+ }
+ }
+
+ String style = null;
+ reqParam = request.getAttribute(REQUEST_APPSTYLE);
+ if (reqParam != null) {
+ style = "style=\"" + reqParam + "\"";
+ }
+ page.write("<div id=\"" + appId + "\" class=\"i-app\" "
+ + (style != null ? style : "") + "></div>\n");
+
+ if (!fragment) {
+ page.write("</body>\n</html>\n");
+ }
+ page.close();
+
+ }
+
+ private boolean isGecko17(HttpServletRequest request) {
+ final WebBrowser browser = WebApplicationContext.getApplicationContext(
+ request.getSession()).getBrowser();
+ if (browser != null && browser.getBrowserApplication() != null) {
+ if (browser.getBrowserApplication().indexOf("rv:1.7.") > 0
+ && browser.getBrowserApplication().indexOf("Gecko") > 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void writeTestingToolsScripts(Writer page,
+ HttpServletRequest request) throws IOException {
+ // Testing Tools script and CSS files are served from Testing Tools
+ // Server
+ String ext = getTestingToolsUri(request);
+ ext = ext.substring(0, ext.lastIndexOf('/'));
+ page.write("<script src=\"" + ext + "/ext/TT.js"
+ + "\" type=\"text/javascript\"></script>\n");
+ page.write("<link rel=\"stylesheet\" href=\"" + ext + "/ext/TT.css"
+ + "\" type=\"text/css\" />\n");
+
+ }
+
+ private String getTestingToolsUri(HttpServletRequest request) {
+ if (testingToolsServerUri == null) {
+ // Default behavior is that Testing Tools Server application exists
+ // on same host as current application does in port 8099.
+ testingToolsServerUri = "http" + "://" + request.getServerName()
+ + ":8099" + "/TestingToolsServer";
+ }
+ return testingToolsServerUri;
+ }
+
+ /**
+ * Handles the requested URI. An application can add handlers to do special
+ * processing, when a certain URI is requested. The handlers are invoked
+ * before any windows URIs are processed and if a DownloadStream is returned
+ * it is sent to the client.
+ *
+ * @param application
+ * the Application owning the URI.
+ * @param request
+ * the HTTP request instance.
+ * @param response
+ * the HTTP response to write to.
+ * @return boolean <code>true</code> if the request was handled and
+ * further processing should be suppressed, <code>false</code>
+ * otherwise.
+ * @see com.itmill.toolkit.terminal.URIHandler
+ */
+ private DownloadStream handleURI(Application application,
+ HttpServletRequest request, HttpServletResponse response) {
+
+ String uri = request.getPathInfo();
+
+ // If no URI is available
+ if (uri == null) {
+ uri = "";
+ }
+
+ // Removes the leading /
+ while (uri.startsWith("/") && uri.length() > 0) {
+ uri = uri.substring(1);
+ }
+
+ // If using application runner, remove package and class name
+ if (isApplicationRunnerServlet) {
+ uri = uri.replaceFirst(applicationRunnerClassname + "/", "");
+ }
+
+ // Handles the uri
+ DownloadStream stream = null;
+ try {
+ stream = application.handleURI(application.getURL(), uri);
+ } catch (final Throwable t) {
+ application.terminalError(new URIHandlerErrorImpl(application, t));
+ }
+
+ return stream;
+ }
+
+ /**
+ * Handles the requested URI. An application can add handlers to do special
+ * processing, when a certain URI is requested. The handlers are invoked
+ * before any windows URIs are processed and if a DownloadStream is returned
+ * it is sent to the client.
+ *
+ * @param stream
+ * the download stream.
+ *
+ * @param request
+ * the HTTP request instance.
+ * @param response
+ * the HTTP response to write to.
+ *
+ * @see com.itmill.toolkit.terminal.URIHandler
+ */
+ private void handleDownload(DownloadStream stream,
+ HttpServletRequest request, HttpServletResponse response) {
+
+ if (stream.getParameter("Location") != null) {
+ response.setStatus(HttpServletResponse.SC_FOUND);
+ response.addHeader("Location", stream.getParameter("Location"));
+ return;
+ }
+
+ // Download from given stream
+ final InputStream data = stream.getStream();
+ if (data != null) {
+
+ // Sets content type
+ response.setContentType(stream.getContentType());
+
+ // Sets cache headers
+ final long cacheTime = stream.getCacheTime();
+ if (cacheTime <= 0) {
+ response.setHeader("Cache-Control", "no-cache");
+ response.setHeader("Pragma", "no-cache");
+ response.setDateHeader("Expires", 0);
+ } else {
+ response.setHeader("Cache-Control", "max-age=" + cacheTime
+ / 1000);
+ response.setDateHeader("Expires", System.currentTimeMillis()
+ + cacheTime);
+ response.setHeader("Pragma", "cache"); // Required to apply
+ // caching in some
+ // Tomcats
+ }
+
+ // Copy download stream parameters directly
+ // to HTTP headers.
+ final Iterator i = stream.getParameterNames();
+ if (i != null) {
+ while (i.hasNext()) {
+ final String param = (String) i.next();
+ response.setHeader(param, stream.getParameter(param));
+ }
+ }
+
+ int bufferSize = stream.getBufferSize();
+ if (bufferSize <= 0 || bufferSize > MAX_BUFFER_SIZE) {
+ bufferSize = DEFAULT_BUFFER_SIZE;
+ }
+ final byte[] buffer = new byte[bufferSize];
+ int bytesRead = 0;
+
+ try {
+ final OutputStream out = response.getOutputStream();
+
+ while ((bytesRead = data.read(buffer)) > 0) {
+ out.write(buffer, 0, bytesRead);
+ out.flush();
+ }
+ out.close();
+ } catch (final IOException ignored) {
+ System.err
+ .println("Warning: ApplicationServlet.handleDownload()"
+ + " threw IOException.");
+ }
+
+ }
+
+ }
+
+ /**
+ * Handles theme resource file requests. Resources supplied with the themes
+ * are provided by the WebAdapterServlet.
+ *
+ * @param request
+ * the HTTP request.
+ * @param response
+ * the HTTP response.
+ * @return boolean <code>true</code> if the request was handled and
+ * further processing should be suppressed, <code>false</code>
+ * otherwise.
+ * @throws ServletException
+ * if an exception has occurred that interferes with the
+ * servlet's normal operation.
+ */
+ private boolean handleResourceRequest(HttpServletRequest request,
+ HttpServletResponse response, String themeName)
+ throws ServletException {
+
+ // If the resource path is unassigned, initialize it
+ if (resourcePath == null) {
+ resourcePath = request.getContextPath() + request.getServletPath()
+ + RESOURCE_URI;
+ // WebSphere Application Server related fix
+ resourcePath = resourcePath.replaceAll("//", "/");
+ }
+
+ String resourceId = request.getPathInfo();
+
+ // Checks if this really is a resource request
+ if (resourceId == null || !resourceId.startsWith(RESOURCE_URI)) {
+ return false;
+ }
+
+ // Checks the resource type
+ resourceId = resourceId.substring(RESOURCE_URI.length());
+ InputStream data = null;
+
+ // Gets theme resources
+ try {
+ data = getServletContext().getResourceAsStream(
+ THEME_DIRECTORY_PATH + themeName + "/" + resourceId);
+ } catch (final Exception e) {
+ e.printStackTrace();
+ data = null;
+ }
+
+ // Writes the response
+ try {
+ if (data != null) {
+ response.setContentType(FileTypeResolver
+ .getMIMEType(resourceId));
+
+ // Use default cache time for theme resources
+ response.setHeader("Cache-Control", "max-age="
+ + DEFAULT_THEME_CACHETIME / 1000);
+ response.setDateHeader("Expires", System.currentTimeMillis()
+ + DEFAULT_THEME_CACHETIME);
+ response.setHeader("Pragma", "cache"); // Required to apply
+ // caching in some
+ // Tomcats
+
+ // Writes the data to client
+ final byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
+ int bytesRead = 0;
+ final OutputStream out = response.getOutputStream();
+ while ((bytesRead = data.read(buffer)) > 0) {
+ out.write(buffer, 0, bytesRead);
+ }
+ out.close();
+ data.close();
+ } else {
+ response.sendError(HttpServletResponse.SC_NOT_FOUND);
+ }
+
+ } catch (final java.io.IOException e) {
+ System.err.println("Resource transfer failed: "
+ + request.getRequestURI() + ". (" + e.getMessage() + ")");
+ }
+
+ return true;
+ }
+
+ /**
+ * Gets the current application URL from request.
+ *
+ * @param request
+ * the HTTP request.
+ * @throws MalformedURLException
+ * if the application is denied access to the persistent
+ * data store represented by the given URL.
+ */
+ private URL getApplicationUrl(HttpServletRequest request)
+ throws MalformedURLException {
+
+ URL applicationUrl;
+ try {
+ final URL reqURL = new URL(
+ (request.isSecure() ? "https://" : "http://")
+ + request.getServerName()
+ + ((request.isSecure() && request.getServerPort() == 443)
+ || (!request.isSecure() && request
+ .getServerPort() == 80) ? "" : ":"
+ + request.getServerPort())
+ + request.getRequestURI());
+ String servletPath = "";
+ if (request.getAttribute("javax.servlet.include.servlet_path") != null) {
+ // this is an include request
+ servletPath = request.getAttribute(
+ "javax.servlet.include.context_path").toString()
+ + request
+ .getAttribute("javax.servlet.include.servlet_path");
+
+ } else {
+ servletPath = request.getContextPath()
+ + request.getServletPath();
+ }
+ if (servletPath.length() == 0
+ || servletPath.charAt(servletPath.length() - 1) != '/') {
+ servletPath = servletPath + "/";
+ }
+ applicationUrl = new URL(reqURL, servletPath);
+ } catch (final MalformedURLException e) {
+ System.err.println("Error constructing application url "
+ + request.getRequestURI() + " (" + e + ")");
+ throw e;
+ }
+
+ return applicationUrl;
+ }
+
+ /**
+ * Parses application runner URIs.
+ *
+ * If request URL is e.g.
+ * http://localhost:8080/itmill/run/com.itmill.toolkit.demo.Calc then
+ * <ul>
+ * <li>context=itmill</li>
+ * <li>Runner servlet=run</li>
+ * <li>Toolkit application=com.itmill.toolkit.demo.Calc</li>
+ * </ul>
+ *
+ * @param request
+ * @return string array containing widgetset URI, application URI and
+ * context, runner, application classname
+ */
+ private String[] getApplicationRunnerURIs(HttpServletRequest request) {
+ final String[] urlParts = request.getRequestURI().toString().split(
+ "\\/");
+ String context = null;
+ String runner = null;
+ String applicationClassname = null;
+ if (urlParts[1].equals(request.getContextPath().replaceAll("\\/", ""))) {
+ // class name comes after web context and runner application
+ context = urlParts[1];
+ runner = urlParts[2];
+ applicationClassname = urlParts[3];
+ return new String[] { "/" + context,
+ "/" + context + "/" + runner + "/" + applicationClassname,
+ context, runner, applicationClassname };
+ } else {
+ // no context
+ context = "";
+ runner = urlParts[1];
+ applicationClassname = urlParts[2];
+ return new String[] { "/",
+ "/" + runner + "/" + applicationClassname, context, runner,
+ applicationClassname };
+ }
+ }
+
+ /**
+ * Gets the existing application for given request. Looks for application
+ * instance for given request based on the requested URL.
+ *
+ * @param request
+ * the HTTP request.
+ * @param response
+ * @return Application instance, or null if the URL does not map to valid
+ * application.
+ * @throws MalformedURLException
+ * if the application is denied access to the persistent
+ * data store represented by the given URL.
+ * @throws SAXException
+ * @throws IllegalAccessException
+ * @throws InstantiationException
+ */
+ private Application getExistingApplication(HttpServletRequest request,
+ HttpServletResponse response) throws MalformedURLException,
+ SAXException, IllegalAccessException, InstantiationException {
+
+ // Ensures that the session is still valid
+ final HttpSession session = request.getSession(true);
+
+ // Gets application list for the session.
+ final Collection applications = WebApplicationContext
+ .getApplicationContext(session).getApplications();
+
+ // Search for the application (using the application URI) from the list
+ for (final Iterator i = applications.iterator(); i.hasNext();) {
+ final Application a = (Application) i.next();
+ final String aPath = a.getURL().getPath();
+ String servletPath = "";
+ if (isApplicationRunnerServlet) {
+ final String[] URIparts = getApplicationRunnerURIs(request);
+ servletPath = URIparts[1] + "/";
+ } else {
+ servletPath = request.getContextPath()
+ + request.getServletPath();
+ if (servletPath.length() < aPath.length()) {
+ servletPath += "/";
+ }
+ }
+ if (servletPath.equals(aPath)) {
+ // Found a running application
+ if (a.isRunning()) {
+ return a;
+ }
+ // Application has stopped, so remove it before creating a new
+ // application
+ WebApplicationContext.getApplicationContext(session)
+ .removeApplication(a);
+ break;
+ }
+ }
+
+ // Existing application not found
+ return null;
+ }
+
+ /**
+ * Creates new application for given request.
+ *
+ * @param request
+ * the HTTP request.
+ * @param response
+ * @return Application instance, or null if the URL does not map to valid
+ * application.
+ * @throws MalformedURLException
+ * if the application is denied access to the persistent
+ * data store represented by the given URL.
+ * @throws SAXException
+ * @throws IllegalAccessException
+ * @throws InstantiationException
+ */
+ private Application getNewApplication(HttpServletRequest request,
+ HttpServletResponse response) throws MalformedURLException,
+ SAXException, IllegalAccessException, InstantiationException {
+
+ // Create application
+ final WebApplicationContext context = WebApplicationContext
+ .getApplicationContext(request.getSession());
+ final URL applicationUrl;
+
+ if (isApplicationRunnerServlet) {
+ final String[] URIparts = getApplicationRunnerURIs(request);
+ final String applicationClassname = URIparts[4];
+ applicationUrl = new URL(getApplicationUrl(request).toString()
+ + applicationClassname + "/");
+ try {
+ applicationClass = classLoader.loadClass(applicationClassname);
+ } catch (final ClassNotFoundException e) {
+ throw new InstantiationException(
+ "Failed to load application class: "
+ + applicationClassname);
+ }
+ } else {
+ applicationUrl = getApplicationUrl(request);
+ }
+
+ // Creates new application and start it
+ try {
+ final Application application = (Application) applicationClass
+ .newInstance();
+ context.addApplication(application);
+
+ // Sets initial locale from the request
+ application.setLocale(request.getLocale());
+
+ // Starts application
+ application.start(applicationUrl, applicationProperties, context);
+
+ return application;
+
+ } catch (final IllegalAccessException e) {
+ System.err.println("Illegal access to application class "
+ + applicationClass.getName());
+ throw e;
+ } catch (final InstantiationException e) {
+ System.err.println("Failed to instantiate application class: "
+ + applicationClass.getName());
+ throw e;
+ }
+ }
+
+ /**
+ * Ends the application.
+ *
+ * @param request
+ * the HTTP request.
+ * @param response
+ * the HTTP response to write to.
+ * @param application
+ * the application to end.
+ * @throws IOException
+ * if the writing failed due to input/output error.
+ */
+ private void endApplication(HttpServletRequest request,
+ HttpServletResponse response, Application application)
+ throws IOException {
+
+ String logoutUrl = application.getLogoutURL();
+ if (logoutUrl == null) {
+ logoutUrl = application.getURL().toString();
+ }
+
+ final HttpSession session = request.getSession();
+ if (session != null) {
+ WebApplicationContext.getApplicationContext(session)
+ .removeApplication(application);
+ }
+
+ response.sendRedirect(response.encodeRedirectURL(logoutUrl));
+ }
+
+ /**
+ * Gets the existing application or create a new one. Get a window within an
+ * application based on the requested URI.
+ *
+ * @param request
+ * the HTTP Request.
+ * @param application
+ * the Application to query for window.
+ * @return Window matching the given URI or null if not found.
+ * @throws ServletException
+ * if an exception has occurred that interferes with the
+ * servlet's normal operation.
+ */
+ private Window getApplicationWindow(HttpServletRequest request,
+ Application application) throws ServletException {
+
+ Window window = null;
+
+ // Finds the window where the request is handled
+ String path = request.getPathInfo();
+
+ // Main window as the URI is empty
+ if (path == null || path.length() == 0 || path.equals("/")) {
+ window = application.getMainWindow();
+ } else {
+ String windowName = null;
+ if (path.charAt(0) == '/') {
+ path = path.substring(1);
+ }
+ final int index = path.indexOf('/');
+ if (index < 0) {
+ windowName = path;
+ path = "";
+ } else {
+ windowName = path.substring(0, index);
+ path = path.substring(index + 1);
+ }
+ window = application.getWindow(windowName);
+
+ if (window == null) {
+ // By default, we use main window
+ window = application.getMainWindow();
+ } else if (!window.isVisible()) {
+ // Implicitly painting without actually invoking paint()
+ window.requestRepaintRequests();
+
+ // If the window is invisible send a blank page
+ return null;
+ }
+ }
+
+ return window;
+ }
+
+ /**
+ * Gets relative location of a theme resource.
+ *
+ * @param theme
+ * the Theme name.
+ * @param resource
+ * the Theme resource.
+ * @return External URI specifying the resource
+ */
+ public String getResourceLocation(String theme, ThemeResource resource) {
+
+ if (resourcePath == null) {
+ return resource.getResourceId();
+ }
+ return resourcePath + theme + "/" + resource.getResourceId();
+ }
+
+ /**
+ * Checks if web adapter is in debug mode. Extra output is generated to log
+ * when debug mode is enabled.
+ *
+ * @param parameters
+ * @return <code>true</code> if the web adapter is in debug mode.
+ * otherwise <code>false</code>.
+ */
+ public boolean isDebugMode(Map parameters) {
+ if (parameters != null) {
+ final Object[] debug = (Object[]) parameters.get("debug");
+ if (debug != null && !"false".equals(debug[0].toString())
+ && !"false".equals(debugMode)) {
+ return true;
+ }
+ }
+ return "true".equals(debugMode);
+ }
+
+ /**
+ * Implementation of ParameterHandler.ErrorEvent interface.
+ */
+ public class ParameterHandlerErrorImpl implements
+ ParameterHandler.ErrorEvent {
+
+ private ParameterHandler owner;
+
+ private Throwable throwable;
+
+ /**
+ * Gets the contained throwable.
+ *
+ * @see com.itmill.toolkit.terminal.Terminal.ErrorEvent#getThrowable()
+ */
+ public Throwable getThrowable() {
+ return throwable;
+ }
+
+ /**
+ * Gets the source ParameterHandler.
+ *
+ * @see com.itmill.toolkit.terminal.ParameterHandler.ErrorEvent#getParameterHandler()
+ */
+ public ParameterHandler getParameterHandler() {
+ return owner;
+ }
+
+ }
+
+ /**
+ * Implementation of URIHandler.ErrorEvent interface.
+ */
+ public class URIHandlerErrorImpl implements URIHandler.ErrorEvent {
+
+ private final URIHandler owner;
+
+ private final Throwable throwable;
+
+ /**
+ *
+ * @param owner
+ * @param throwable
+ */
+ private URIHandlerErrorImpl(URIHandler owner, Throwable throwable) {
+ this.owner = owner;
+ this.throwable = throwable;
+ }
+
+ /**
+ * Gets the contained throwable.
+ *
+ * @see com.itmill.toolkit.terminal.Terminal.ErrorEvent#getThrowable()
+ */
+ public Throwable getThrowable() {
+ return throwable;
+ }
+
+ /**
+ * Gets the source URIHandler.
+ *
+ * @see com.itmill.toolkit.terminal.URIHandler.ErrorEvent#getURIHandler()
+ */
+ public URIHandler getURIHandler() {
+ return owner;
+ }
+ }
+
+ /**
+ * Gets communication manager for an application.
+ *
+ * If this application has not been running before, new manager is created.
+ *
+ * @param application
+ * @return CommunicationManager
+ */
+ private CommunicationManager getApplicationManager(Application application) {
+ CommunicationManager mgr = (CommunicationManager) applicationToAjaxAppMgrMap
+ .get(application);
+
+ if (mgr == null) {
+ // Creates new manager
+ mgr = new CommunicationManager(application);
+ applicationToAjaxAppMgrMap.put(application, mgr);
+ }
+ return mgr;
+ }
+
+ /**
+ * Gets resource path using different implementations. Required to
+ * supporting different servlet container implementations (application
+ * servers).
+ *
+ * @param servletContext
+ * @param path
+ * the resource path.
+ * @return the resource path.
+ */
+ protected static String getResourcePath(ServletContext servletContext,
+ String path) {
+ String resultPath = null;
+ resultPath = servletContext.getRealPath(path);
+ if (resultPath != null) {
+ return resultPath;
+ } else {
+ try {
+ final URL url = servletContext.getResource(path);
+ resultPath = url.getFile();
+ } catch (final Exception e) {
+ e.printStackTrace();
+ }
+ }
+ return resultPath;
+ }
+
+}
\ No newline at end of file
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.server;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.lang.reflect.Method;
+import java.net.SocketException;
+import java.text.DateFormatSymbols;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import com.itmill.toolkit.Application;
+import com.itmill.toolkit.Application.SystemMessages;
+import com.itmill.toolkit.external.org.apache.commons.fileupload.FileItemIterator;
+import com.itmill.toolkit.external.org.apache.commons.fileupload.FileItemStream;
+import com.itmill.toolkit.external.org.apache.commons.fileupload.FileUploadException;
+import com.itmill.toolkit.external.org.apache.commons.fileupload.ProgressListener;
+import com.itmill.toolkit.external.org.apache.commons.fileupload.servlet.ServletFileUpload;
+import com.itmill.toolkit.terminal.Paintable;
+import com.itmill.toolkit.terminal.URIHandler;
+import com.itmill.toolkit.terminal.UploadStream;
+import com.itmill.toolkit.terminal.VariableOwner;
+import com.itmill.toolkit.terminal.Paintable.RepaintRequestEvent;
+import com.itmill.toolkit.ui.Component;
+import com.itmill.toolkit.ui.Upload;
+import com.itmill.toolkit.ui.Window;
+
+/**
+ * Application manager processes changes and paints for single application
+ * instance.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 5.0
+ */
+public class CommunicationManager implements Paintable.RepaintRequestListener {
+
+ private static String GET_PARAM_REPAINT_ALL = "repaintAll";
+
+ /* Variable records indexes */
+ private static final int VAR_PID = 1;
+ private static final int VAR_NAME = 2;
+ private static final int VAR_TYPE = 3;
+ private static final int VAR_VALUE = 0;
+
+ private static final String VAR_RECORD_SEPARATOR = "\u001e";
+
+ private static final String VAR_FIELD_SEPARATOR = "\u001f";
+
+ private static final int MAX_BUFFER_SIZE = 64 * 1024;
+
+ private final ArrayList dirtyPaintabletSet = new ArrayList();
+
+ private final HashMap paintableIdMap = new HashMap();
+
+ private final HashMap idPaintableMap = new HashMap();
+
+ private int idSequence = 0;
+
+ private final Application application;
+
+ private List locales;
+
+ private int pendingLocalesIndex;
+
+ public CommunicationManager(Application application) {
+ this.application = application;
+ requireLocale(application.getLocale().toString());
+ }
+
+ /**
+ * Handles file upload request submitted via Upload component.
+ *
+ * @param request
+ * @param response
+ * @throws IOException
+ */
+ public void handleFileUpload(HttpServletRequest request,
+ HttpServletResponse response) throws IOException {
+ // Create a new file upload handler
+ final ServletFileUpload upload = new ServletFileUpload();
+
+ final UploadProgressListener pl = new UploadProgressListener();
+
+ upload.setProgressListener(pl);
+
+ // Parse the request
+ FileItemIterator iter;
+
+ try {
+ iter = upload.getItemIterator(request);
+ /*
+ * ATM this loop is run only once as we are uploading one file per
+ * request.
+ */
+ while (iter.hasNext()) {
+ final FileItemStream item = iter.next();
+ final String name = item.getFieldName();
+ final String filename = item.getName();
+ final String mimeType = item.getContentType();
+ final InputStream stream = item.openStream();
+ if (item.isFormField()) {
+ // ignored, upload requests contains only files
+ } else {
+ final String pid = name.split("_")[0];
+ final Upload uploadComponent = (Upload) idPaintableMap
+ .get(pid);
+ if (uploadComponent.isReadOnly()) {
+ throw new FileUploadException(
+ "Warning: ignored file upload because upload component is set as read-only");
+ }
+ if (uploadComponent == null) {
+ throw new FileUploadException(
+ "Upload component not found");
+ }
+ synchronized (application) {
+ // put upload component into receiving state
+ uploadComponent.startUpload();
+ }
+ final UploadStream upstream = new UploadStream() {
+
+ public String getContentName() {
+ return filename;
+ }
+
+ public String getContentType() {
+ return mimeType;
+ }
+
+ public InputStream getStream() {
+ return stream;
+ }
+
+ public String getStreamName() {
+ return "stream";
+ }
+
+ };
+
+ // tell UploadProgressListener which component is receiving
+ // file
+ pl.setUpload(uploadComponent);
+
+ uploadComponent.receiveUpload(upstream);
+ }
+ }
+ } catch (final FileUploadException e) {
+ e.printStackTrace();
+ }
+
+ // Send short response to acknowledge client that request was done
+ response.setContentType("text/html");
+ final OutputStream out = response.getOutputStream();
+ final PrintWriter outWriter = new PrintWriter(new BufferedWriter(
+ new OutputStreamWriter(out, "UTF-8")));
+ outWriter.print("<html><body>download handled</body></html>");
+ outWriter.flush();
+ out.close();
+ }
+
+ /**
+ * Handles UIDL request
+ *
+ * @param request
+ * @param response
+ * @throws IOException
+ */
+ public void handleUidlRequest(HttpServletRequest request,
+ HttpServletResponse response, ApplicationServlet applicationServlet)
+ throws IOException {
+
+ // repaint requested or session has timed out and new one is created
+ boolean repaintAll = (request.getParameter(GET_PARAM_REPAINT_ALL) != null)
+ || request.getSession().isNew();
+
+ final OutputStream out = response.getOutputStream();
+ final PrintWriter outWriter = new PrintWriter(new BufferedWriter(
+ new OutputStreamWriter(out, "UTF-8")));
+
+ try {
+
+ // The rest of the process is synchronized with the application
+ // in order to guarantee that no parallel variable handling is
+ // made
+ synchronized (application) {
+
+ // Finds the window within the application
+ Window window = null;
+ if (application.isRunning()) {
+ window = getApplicationWindow(request, application);
+ // Returns if no window found
+ if (window == null) {
+ // This should not happen, no windows exists but
+ // application is still open.
+ System.err
+ .println("Warning, could not get window for application with request URI "
+ + request.getRequestURI());
+ return;
+ }
+ } else {
+ // application has been closed
+ endApplication(request, response, application);
+ return;
+ }
+
+ // Change all variables based on request parameters
+ if (!handleVariables(request, application)) {
+ // var inconsistency; the client is probably out-of-sync
+ SystemMessages ci = null;
+ try {
+ Method m = application.getClass().getMethod(
+ "getSystemMessages", null);
+ ci = (Application.SystemMessages) m.invoke(null, null);
+ } catch (Exception e2) {
+ // Not critical, but something is still wrong; print
+ // stacktrace
+ e2.printStackTrace();
+ }
+ if (ci != null) {
+ String msg = ci.getOutOfSyncMessage();
+ String cap = ci.getOutOfSyncCaption();
+ if (msg != null || cap != null) {
+ applicationServlet.criticalNotification(request,
+ response, cap, msg, ci.getOutOfSyncURL());
+ // will reload page after this
+ return;
+ }
+ }
+ // No message to show, let's just repaint all.
+ System.err
+ .println("Warning: variable inconsistency - client is probably out-of-sync, repainting all.");
+ repaintAll = true;
+
+ }
+
+ // If repaint is requested, clean all ids in this root window
+ if (repaintAll) {
+ for (final Iterator it = idPaintableMap.keySet().iterator(); it
+ .hasNext();) {
+ final Component c = (Component) idPaintableMap.get(it
+ .next());
+ if (isChildOf(window, c)) {
+ it.remove();
+ paintableIdMap.remove(c);
+ }
+ }
+ }
+
+ // Removes application if it has stopped during variable changes
+ if (!application.isRunning()) {
+ endApplication(request, response, application);
+ return;
+ }
+
+ // Sets the response type
+ response.setContentType("application/json; charset=UTF-8");
+ // some dirt to prevent cross site scripting
+ outWriter.print("for(;;);[{");
+
+ outWriter.print("\"changes\":[");
+
+ // re-get mainwindow - may have been changed
+ Window newWindow = getApplicationWindow(request, application);
+ if (newWindow != window) {
+ window = newWindow;
+ repaintAll = true;
+ }
+
+ JsonPaintTarget paintTarget = new JsonPaintTarget(this,
+ outWriter, !repaintAll);
+
+ // Paints components
+ ArrayList paintables;
+ if (repaintAll) {
+ paintables = new ArrayList();
+ paintables.add(window);
+
+ // Reset sent locales
+ locales = null;
+ requireLocale(application.getLocale().toString());
+
+ } else {
+ // remove detached components from paintableIdMap so they
+ // can be GC'ed
+ for (Iterator it = paintableIdMap.keySet().iterator(); it
+ .hasNext();) {
+ Component p = (Component) it.next();
+ if (p.getApplication() == null) {
+ idPaintableMap.remove(paintableIdMap.get(p));
+ it.remove();
+ dirtyPaintabletSet.remove(p);
+ p.removeListener(this);
+ }
+ }
+ paintables = getDirtyComponents(window);
+ }
+ if (paintables != null) {
+
+ // We need to avoid painting children before parent.
+ // This is ensured by ordering list by depth in component
+ // tree
+ Collections.sort(paintables, new Comparator() {
+ public int compare(Object o1, Object o2) {
+ Component c1 = (Component) o1;
+ Component c2 = (Component) o2;
+ int d1 = 0;
+ while (c1.getParent() != null) {
+ d1++;
+ c1 = c1.getParent();
+ }
+ int d2 = 0;
+ while (c2.getParent() != null) {
+ d2++;
+ c2 = c2.getParent();
+ }
+ if (d1 < d2) {
+ return -1;
+ }
+ if (d1 > d2) {
+ return 1;
+ }
+ return 0;
+ }
+ });
+
+ for (final Iterator i = paintables.iterator(); i.hasNext();) {
+ final Paintable p = (Paintable) i.next();
+
+ // TODO CLEAN
+ if (p instanceof Window) {
+ final Window w = (Window) p;
+ if (w.getTerminal() == null) {
+ w.setTerminal(application.getMainWindow()
+ .getTerminal());
+ }
+ }
+ /*
+ * This does not seem to happen in tk5, but remember
+ * this case: else if (p instanceof Component) { if
+ * (((Component) p).getParent() == null || ((Component)
+ * p).getApplication() == null) { // Component requested
+ * repaint, but is no // longer attached: skip
+ * paintablePainted(p); continue; } }
+ */
+
+ // TODO we may still get changes that have been
+ // rendered already (changes with only cached flag)
+ if (paintTarget.needsToBePainted(p)) {
+ paintTarget.startTag("change");
+ paintTarget.addAttribute("format", "uidl");
+ final String pid = getPaintableId(p);
+ paintTarget.addAttribute("pid", pid);
+
+ p.paint(paintTarget);
+
+ paintTarget.endTag("change");
+ }
+ paintablePainted(p);
+ }
+ }
+
+ paintTarget.close();
+ outWriter.print("]"); // close changes
+
+ outWriter.print(", \"meta\" : {");
+ boolean metaOpen = false;
+
+ if (repaintAll) {
+ metaOpen = true;
+ outWriter.write("\"repaintAll\":true");
+ }
+
+ // add meta instruction for client to set focus if it is set
+ final Paintable f = (Paintable) application.consumeFocus();
+ if (f != null) {
+ if (metaOpen) {
+ outWriter.write(",");
+ }
+ outWriter.write("\"focus\":\"" + getPaintableId(f) + "\"");
+ }
+
+ outWriter.print("}, \"resources\" : {");
+
+ // Precache custom layouts
+ String themeName = window.getTheme();
+ if (request.getParameter("theme") != null) {
+ themeName = request.getParameter("theme");
+ }
+ if (themeName == null) {
+ themeName = "default";
+ }
+
+ // TODO We should only precache the layouts that are not
+ // cached already
+ int resourceIndex = 0;
+ for (final Iterator i = paintTarget.getPreCachedResources()
+ .iterator(); i.hasNext();) {
+ final String resource = (String) i.next();
+ InputStream is = null;
+ try {
+ is = applicationServlet
+ .getServletContext()
+ .getResourceAsStream(
+ "/"
+ + ApplicationServlet.THEME_DIRECTORY_PATH
+ + themeName + "/" + resource);
+ } catch (final Exception e) {
+ e.printStackTrace();
+ }
+ if (is != null) {
+
+ outWriter.print((resourceIndex++ > 0 ? ", " : "")
+ + "\"" + resource + "\" : ");
+ final StringBuffer layout = new StringBuffer();
+
+ try {
+ final InputStreamReader r = new InputStreamReader(
+ is);
+ final char[] buffer = new char[20000];
+ int charsRead = 0;
+ while ((charsRead = r.read(buffer)) > 0) {
+ layout.append(buffer, 0, charsRead);
+ }
+ r.close();
+ } catch (final java.io.IOException e) {
+ System.err.println("Resource transfer failed: "
+ + request.getRequestURI() + ". ("
+ + e.getMessage() + ")");
+ }
+ outWriter.print("\""
+ + JsonPaintTarget.escapeJSON(layout.toString())
+ + "\"");
+ } else {
+ System.err.println("CustomLayout " + "/"
+ + ApplicationServlet.THEME_DIRECTORY_PATH
+ + themeName + "/" + resource + " not found!");
+ }
+ }
+ outWriter.print("}");
+
+ printLocaleDeclarations(outWriter);
+
+ outWriter.print("}]");
+
+ outWriter.flush();
+ outWriter.close();
+ }
+
+ out.flush();
+ out.close();
+ } catch (SocketException e) {
+ // Most likely client browser closed socket
+ System.err
+ .println("Warning: SocketException in CommunicationManager."
+ + " Most likely client (browser) closed socket.");
+ } catch (final Throwable e) {
+ e.printStackTrace();
+ // Writes the error report to client
+ // FIXME breaks UIDL response, security shouldn't reveal stack trace
+ // to client side
+ final OutputStreamWriter w = new OutputStreamWriter(out);
+ final PrintWriter err = new PrintWriter(w);
+ err
+ .write("<html><head><title>Application Internal Error</title></head><body>");
+ err.write("<h1>" + e.toString() + "</h1><pre>\n");
+ e.printStackTrace(new PrintWriter(err));
+ err.write("\n</pre></body></html>");
+ err.close();
+ }
+ }
+
+ /**
+ * If this method returns false, something was submitted that we did not
+ * expect; this is probably due to the client being out-of-sync and sending
+ * variable changes for non-existing pids
+ *
+ * @param request
+ * @param application2
+ * @return true if successful, false if there was an inconsistency
+ * @throws IOException
+ */
+ private boolean handleVariables(HttpServletRequest request,
+ Application application2) throws IOException {
+ boolean success = true;
+
+ if (request.getContentLength() > 0) {
+
+ byte[] buffer = new byte[request.getContentLength()];
+ ServletInputStream inputStream = request.getInputStream();
+ int totalBytesRead = 0;
+ int bytesRead;
+ while ((bytesRead = inputStream.read(buffer, totalBytesRead,
+ MAX_BUFFER_SIZE)) != -1) {
+ totalBytesRead += bytesRead;
+ }
+
+ String changes = new String(buffer, "utf-8");
+ // extract variables to two dim string array
+ final String[] tmp = changes.split(VAR_RECORD_SEPARATOR);
+ final String[][] variableRecords = new String[tmp.length][4];
+ for (int i = 0; i < tmp.length; i++) {
+ variableRecords[i] = tmp[i].split(VAR_FIELD_SEPARATOR);
+ }
+
+ for (int i = 0; i < variableRecords.length; i++) {
+ String[] variable = variableRecords[i];
+ String[] nextVariable = null;
+ if (i + 1 < variableRecords.length) {
+ nextVariable = variableRecords[i + 1];
+ }
+ final VariableOwner owner = (VariableOwner) idPaintableMap
+ .get(variable[VAR_PID]);
+ if (owner != null && owner.isEnabled()) {
+ Map m;
+ if (nextVariable != null
+ && variable[VAR_PID].equals(nextVariable[VAR_PID])) {
+ // we have more than one value changes in row for one
+ // variable owner, collect em in HashMap
+ m = new HashMap();
+ m.put(variable[VAR_NAME], convertVariableValue(
+ variable[VAR_TYPE].charAt(0),
+ variable[VAR_VALUE]));
+ } else {
+ // use optimized single value map
+ m = new SingleValueMap(variable[VAR_NAME],
+ convertVariableValue(variable[VAR_TYPE]
+ .charAt(0), variable[VAR_VALUE]));
+ }
+
+ // collect following variable changes for this owner
+ while (nextVariable != null
+ && variable[VAR_PID].equals(nextVariable[VAR_PID])) {
+ i++;
+ variable = nextVariable;
+ if (i + 1 < variableRecords.length) {
+ nextVariable = variableRecords[i + 1];
+ } else {
+ nextVariable = null;
+ }
+ m.put(variable[VAR_NAME], convertVariableValue(
+ variable[VAR_TYPE].charAt(0),
+ variable[VAR_VALUE]));
+ }
+ owner.changeVariables(request, m);
+ } else {
+ // Ignore variable change
+ String msg = "Warning: Ignoring variable change for ";
+ if (owner != null) {
+ msg += "disabled component " + owner.getClass();
+ String caption = ((Component) owner).getCaption();
+ if (caption != null) {
+ msg += ", caption=" + caption;
+ }
+ } else {
+ msg += "non-existent component, VAR_PID="
+ + variable[VAR_PID];
+ success = false;
+ }
+ System.err.println(msg);
+ continue;
+ }
+ }
+ }
+ return success;
+ }
+
+ private Object convertVariableValue(char variableType, String strValue) {
+ Object val = null;
+ switch (variableType) {
+ case 'a':
+ val = strValue.split(",");
+ break;
+ case 's':
+ val = strValue;
+ break;
+ case 'i':
+ val = Integer.valueOf(strValue);
+ break;
+ case 'l':
+ val = Long.valueOf(strValue);
+ case 'f':
+ val = Float.valueOf(strValue);
+ break;
+ case 'd':
+ val = Double.valueOf(strValue);
+ break;
+ case 'b':
+ val = Boolean.valueOf(strValue);
+ break;
+ }
+
+ return val;
+ }
+
+ private void printLocaleDeclarations(PrintWriter outWriter) {
+ /*
+ * ----------------------------- Sending Locale sensitive date
+ * -----------------------------
+ */
+
+ // Store JVM default locale for later restoration
+ // (we'll have to change the default locale for a while)
+ final Locale jvmDefault = Locale.getDefault();
+
+ // Send locale informations to client
+ outWriter.print(", \"locales\":[");
+ for (; pendingLocalesIndex < locales.size(); pendingLocalesIndex++) {
+
+ final Locale l = generateLocale((String) locales
+ .get(pendingLocalesIndex));
+ // Locale name
+ outWriter.print("{\"name\":\"" + l.toString() + "\",");
+
+ /*
+ * Month names (both short and full)
+ */
+ final DateFormatSymbols dfs = new DateFormatSymbols(l);
+ final String[] short_months = dfs.getShortMonths();
+ final String[] months = dfs.getMonths();
+ outWriter.print("\"smn\":[\""
+ + // ShortMonthNames
+ short_months[0] + "\",\"" + short_months[1] + "\",\""
+ + short_months[2] + "\",\"" + short_months[3] + "\",\""
+ + short_months[4] + "\",\"" + short_months[5] + "\",\""
+ + short_months[6] + "\",\"" + short_months[7] + "\",\""
+ + short_months[8] + "\",\"" + short_months[9] + "\",\""
+ + short_months[10] + "\",\"" + short_months[11] + "\""
+ + "],");
+ outWriter.print("\"mn\":[\""
+ + // MonthNames
+ months[0] + "\",\"" + months[1] + "\",\"" + months[2]
+ + "\",\"" + months[3] + "\",\"" + months[4] + "\",\""
+ + months[5] + "\",\"" + months[6] + "\",\"" + months[7]
+ + "\",\"" + months[8] + "\",\"" + months[9] + "\",\""
+ + months[10] + "\",\"" + months[11] + "\"" + "],");
+
+ /*
+ * Weekday names (both short and full)
+ */
+ final String[] short_days = dfs.getShortWeekdays();
+ final String[] days = dfs.getWeekdays();
+ outWriter.print("\"sdn\":[\""
+ + // ShortDayNames
+ short_days[1] + "\",\"" + short_days[2] + "\",\""
+ + short_days[3] + "\",\"" + short_days[4] + "\",\""
+ + short_days[5] + "\",\"" + short_days[6] + "\",\""
+ + short_days[7] + "\"" + "],");
+ outWriter.print("\"dn\":[\""
+ + // DayNames
+ days[1] + "\",\"" + days[2] + "\",\"" + days[3] + "\",\""
+ + days[4] + "\",\"" + days[5] + "\",\"" + days[6] + "\",\""
+ + days[7] + "\"" + "],");
+
+ /*
+ * First day of week (0 = sunday, 1 = monday)
+ */
+ final Calendar cal = new GregorianCalendar(l);
+ outWriter.print("\"fdow\":" + (cal.getFirstDayOfWeek() - 1) + ",");
+
+ /*
+ * Date formatting (MM/DD/YYYY etc.)
+ */
+ // Force our locale as JVM default for a while (SimpleDateFormat
+ // uses JVM default)
+ Locale.setDefault(l);
+ final String df = new SimpleDateFormat().toPattern();
+ int timeStart = df.indexOf("H");
+ if (timeStart < 0) {
+ timeStart = df.indexOf("h");
+ }
+ final int ampm_first = df.indexOf("a");
+ // E.g. in Korean locale AM/PM is before h:mm
+ // TODO should take that into consideration on client-side as well,
+ // now always h:mm a
+ if (ampm_first > 0 && ampm_first < timeStart) {
+ timeStart = ampm_first;
+ }
+ final String dateformat = df.substring(0, timeStart - 1);
+
+ outWriter.print("\"df\":\"" + dateformat.trim() + "\",");
+
+ /*
+ * Time formatting (24 or 12 hour clock and AM/PM suffixes)
+ */
+ final String timeformat = df.substring(timeStart, df.length()); // Doesn't
+ // return
+ // second
+ // or
+ // milliseconds
+ // We use timeformat to determine 12/24-hour clock
+ final boolean twelve_hour_clock = timeformat.indexOf("a") > -1;
+ // TODO there are other possibilities as well, like 'h' in french
+ // (ignore them, too complicated)
+ final String hour_min_delimiter = timeformat.indexOf(".") > -1 ? "."
+ : ":";
+ // outWriter.print("\"tf\":\"" + timeformat + "\",");
+ outWriter.print("\"thc\":" + twelve_hour_clock + ",");
+ outWriter.print("\"hmd\":\"" + hour_min_delimiter + "\"");
+ if (twelve_hour_clock) {
+ final String[] ampm = dfs.getAmPmStrings();
+ outWriter.print(",\"ampm\":[\"" + ampm[0] + "\",\"" + ampm[1]
+ + "\"]");
+ }
+ outWriter.print("}");
+ if (pendingLocalesIndex < locales.size() - 1) {
+ outWriter.print(",");
+ }
+ }
+ outWriter.print("]"); // Close locales
+
+ // Restore JVM default locale
+ Locale.setDefault(jvmDefault);
+ }
+
+ /**
+ * Gets the existing application or create a new one. Get a window within an
+ * application based on the requested URI.
+ *
+ * @param request
+ * the HTTP Request.
+ * @param application
+ * the Application to query for window.
+ * @return Window mathing the given URI or null if not found.
+ * @throws ServletException
+ * if an exception has occurred that interferes with the
+ * servlet's normal operation.
+ */
+ private Window getApplicationWindow(HttpServletRequest request,
+ Application application) throws ServletException {
+
+ Window window = null;
+
+ // Find the window where the request is handled
+ String path = request.getPathInfo();
+
+ // Remove UIDL from the path
+ path = path.substring("/UIDL".length());
+
+ // Main window as the URI is empty
+ if (path == null || path.length() == 0 || path.equals("/")) {
+ window = application.getMainWindow();
+ } else {
+ String windowName = null;
+ if (path.charAt(0) == '/') {
+ path = path.substring(1);
+ }
+ final int index = path.indexOf('/');
+ if (index < 0) {
+ windowName = path;
+ path = "";
+ } else {
+ windowName = path.substring(0, index);
+ path = path.substring(index + 1);
+ }
+ window = application.getWindow(windowName);
+
+ // By default, we use main window
+ if (window == null) {
+ window = application.getMainWindow();
+ }
+ }
+
+ return window;
+ }
+
+ /**
+ * Ends the Application.
+ *
+ * @param request
+ * the HTTP request instance.
+ * @param response
+ * the HTTP response to write to.
+ * @param application
+ * the Application to end.
+ * @throws IOException
+ * if the writing failed due to input/output error.
+ */
+ private void endApplication(HttpServletRequest request,
+ HttpServletResponse response, Application application)
+ throws IOException {
+
+ String logoutUrl = application.getLogoutURL();
+ if (logoutUrl == null) {
+ logoutUrl = application.getURL().toString();
+ }
+ // clients JS app is still running, send a special json file to
+ // tell client that application has quit and where to point browser now
+ // Set the response type
+ response.setContentType("application/json; charset=UTF-8");
+ final ServletOutputStream out = response.getOutputStream();
+ final PrintWriter outWriter = new PrintWriter(new BufferedWriter(
+ new OutputStreamWriter(out, "UTF-8")));
+ outWriter.print("for(;;);[{");
+ outWriter.print("\"redirect\":{");
+ outWriter.write("\"url\":\"" + logoutUrl + "\"}}]");
+ outWriter.flush();
+ outWriter.close();
+ out.flush();
+ }
+
+ /**
+ * Gets the Paintable Id. If Paintable has debug id set it will be used
+ * prefixed with "PID_S". Otherwise a sequenced ID is created.
+ *
+ * @param paintable
+ * @return the paintable Id.
+ */
+ public String getPaintableId(Paintable paintable) {
+
+ String id = (String) paintableIdMap.get(paintable);
+ if (id == null) {
+ // use testing identifier as id if set
+ id = paintable.getDebugId();
+ if (id == null) {
+ id = "PID" + Integer.toString(idSequence++);
+ } else {
+ id = "PID_S" + id;
+ }
+ paintableIdMap.put(paintable, id);
+ idPaintableMap.put(id, paintable);
+ }
+
+ return id;
+ }
+
+ public boolean hasPaintableId(Paintable paintable) {
+ return paintableIdMap.containsKey(paintable);
+ }
+
+ /**
+ * @param w
+ * root window for which dirty components is to be fetched
+ * @return
+ */
+ private ArrayList getDirtyComponents(Window w) {
+ final ArrayList resultset = new ArrayList(dirtyPaintabletSet);
+
+ // The following algorithm removes any components that would be painted
+ // as
+ // a direct descendant of other components from the dirty components
+ // list.
+ // The result is that each component should be painted exactly once and
+ // any unmodified components will be painted as "cached=true".
+
+ for (final Iterator i = dirtyPaintabletSet.iterator(); i.hasNext();) {
+ final Paintable p = (Paintable) i.next();
+ if (p instanceof Component) {
+ final Component component = (Component) p;
+ if (component.getApplication() == null) {
+ // component is detached after requestRepaint is called
+ resultset.remove(p);
+ i.remove();
+ } else {
+ Window componentsRoot = component.getWindow();
+ if (componentsRoot.getParent() != null) {
+ // this is a subwindow
+ componentsRoot = (Window) componentsRoot.getParent();
+ }
+ if (componentsRoot != w) {
+ resultset.remove(p);
+ }
+ }
+ }
+ }
+
+ return resultset;
+ }
+
+ /**
+ * @see com.itmill.toolkit.terminal.Paintable.RepaintRequestListener#repaintRequested(com.itmill.toolkit.terminal.Paintable.RepaintRequestEvent)
+ */
+ public void repaintRequested(RepaintRequestEvent event) {
+ final Paintable p = event.getPaintable();
+ if (!dirtyPaintabletSet.contains(p)) {
+ dirtyPaintabletSet.add(p);
+ }
+ }
+
+ /**
+ *
+ * @param p
+ */
+ private void paintablePainted(Paintable p) {
+ dirtyPaintabletSet.remove(p);
+ p.requestRepaintRequests();
+ }
+
+ private final class SingleValueMap implements Map {
+ private final String name;
+
+ private final Object value;
+
+ private SingleValueMap(String name, Object value) {
+ this.name = name;
+ this.value = value;
+ }
+
+ public void clear() {
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean containsKey(Object key) {
+ if (name == null) {
+ return key == null;
+ }
+ return name.equals(key);
+ }
+
+ public boolean containsValue(Object v) {
+ if (value == null) {
+ return v == null;
+ }
+ return value.equals(v);
+ }
+
+ public Set entrySet() {
+ final Set s = new HashSet();
+ s.add(new Map.Entry() {
+
+ public Object getKey() {
+ return name;
+ }
+
+ public Object getValue() {
+ return value;
+ }
+
+ public Object setValue(Object value) {
+ throw new UnsupportedOperationException();
+ }
+ });
+ return s;
+ }
+
+ public Object get(Object key) {
+ if (!name.equals(key)) {
+ return null;
+ }
+ return value;
+ }
+
+ public boolean isEmpty() {
+ return false;
+ }
+
+ public Set keySet() {
+ final Set s = new HashSet();
+ s.add(name);
+ return s;
+ }
+
+ public Object put(Object key, Object value) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void putAll(Map t) {
+ throw new UnsupportedOperationException();
+ }
+
+ public Object remove(Object key) {
+ throw new UnsupportedOperationException();
+ }
+
+ public int size() {
+ return 1;
+ }
+
+ public Collection values() {
+ final LinkedList s = new LinkedList();
+ s.add(value);
+ return s;
+
+ }
+ }
+
+ /**
+ * Implementation of URIHandler.ErrorEvent interface.
+ */
+ public class URIHandlerErrorImpl implements URIHandler.ErrorEvent {
+
+ private final URIHandler owner;
+
+ private final Throwable throwable;
+
+ /**
+ *
+ * @param owner
+ * @param throwable
+ */
+ private URIHandlerErrorImpl(URIHandler owner, Throwable throwable) {
+ this.owner = owner;
+ this.throwable = throwable;
+ }
+
+ /**
+ * @see com.itmill.toolkit.terminal.Terminal.ErrorEvent#getThrowable()
+ */
+ public Throwable getThrowable() {
+ return throwable;
+ }
+
+ /**
+ * @see com.itmill.toolkit.terminal.URIHandler.ErrorEvent#getURIHandler()
+ */
+ public URIHandler getURIHandler() {
+ return owner;
+ }
+ }
+
+ public void requireLocale(String value) {
+ if (locales == null) {
+ locales = new ArrayList();
+ locales.add(application.getLocale().toString());
+ pendingLocalesIndex = 0;
+ }
+ if (!locales.contains(value)) {
+ locales.add(value);
+ }
+ }
+
+ private Locale generateLocale(String value) {
+ final String[] temp = value.split("_");
+ if (temp.length == 1) {
+ return new Locale(temp[0]);
+ } else if (temp.length == 2) {
+ return new Locale(temp[0], temp[1]);
+ } else {
+ return new Locale(temp[0], temp[1], temp[2]);
+ }
+ }
+
+ /*
+ * Upload progress listener notifies upload component once when Jakarta
+ * FileUpload can determine content length. Used to detect files total size,
+ * uploads progress can be tracked inside upload.
+ */
+ private class UploadProgressListener implements ProgressListener {
+ Upload uploadComponent;
+
+ boolean updated = false;
+
+ public void setUpload(Upload u) {
+ uploadComponent = u;
+ }
+
+ public void update(long bytesRead, long contentLength, int items) {
+ if (!updated && uploadComponent != null) {
+ uploadComponent.setUploadSize(contentLength);
+ updated = true;
+ }
+ }
+ }
+
+ /**
+ * Helper method to test if a component contains another
+ *
+ * @param parent
+ * @param child
+ */
+ private static boolean isChildOf(Component parent, Component child) {
+ Component p = child.getParent();
+ while (p != null) {
+ if (parent == p) {
+ return true;
+ }
+ p = p.getParent();
+ }
+ return false;
+ }
+
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.server;
+
+import java.io.InputStream;
+
+/**
+ * AjaxAdapter implementation of the UploadStream interface.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 5.0
+ */
+public class HttpUploadStream implements
+ com.itmill.toolkit.terminal.UploadStream {
+
+ /**
+ * Holds value of property variableName.
+ */
+ private final String streamName;
+
+ private final String contentName;
+
+ private final String contentType;
+
+ /**
+ * Holds value of property variableValue.
+ */
+ private final InputStream stream;
+
+ /**
+ * Creates a new instance of UploadStreamImpl.
+ *
+ * @param name
+ * the name of the stream.
+ * @param stream
+ * the input stream.
+ * @param contentName
+ * the name of the content.
+ * @param contentType
+ * the type of the content.
+ */
+ public HttpUploadStream(String name, InputStream stream,
+ String contentName, String contentType) {
+ streamName = name;
+ this.stream = stream;
+ this.contentName = contentName;
+ this.contentType = contentType;
+ }
+
+ /**
+ * Gets the name of the stream.
+ *
+ * @return the name of the stream.
+ */
+ public String getStreamName() {
+ return streamName;
+ }
+
+ /**
+ * Gets the input stream.
+ *
+ * @return the Input stream.
+ */
+ public InputStream getStream() {
+ return stream;
+ }
+
+ /**
+ * Gets the input stream content type.
+ *
+ * @return the content type of the input stream.
+ */
+ public String getContentType() {
+ return contentType;
+ }
+
+ /**
+ * Gets the stream content name. Stream content name usually differs from
+ * the actual stream name. It is used to identify the content of the stream.
+ *
+ * @return the Name of the stream content.
+ */
+ public String getContentName() {
+ return contentName;
+ }
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.server;
+
+import java.io.PrintWriter;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.Stack;
+import java.util.Vector;
+
+import com.itmill.toolkit.Application;
+import com.itmill.toolkit.terminal.ApplicationResource;
+import com.itmill.toolkit.terminal.ExternalResource;
+import com.itmill.toolkit.terminal.PaintException;
+import com.itmill.toolkit.terminal.PaintTarget;
+import com.itmill.toolkit.terminal.Paintable;
+import com.itmill.toolkit.terminal.Resource;
+import com.itmill.toolkit.terminal.ThemeResource;
+import com.itmill.toolkit.terminal.VariableOwner;
+import com.itmill.toolkit.ui.Component;
+
+/**
+ * User Interface Description Language Target.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 5.0
+ */
+public class JsonPaintTarget implements PaintTarget {
+
+ /* Document type declarations */
+
+ private final static String UIDL_ARG_NAME = "name";
+
+ private final Stack mOpenTags;
+
+ private final Stack openJsonTags;
+
+ private final PrintWriter uidlBuffer;
+
+ private boolean closed = false;
+
+ private final CommunicationManager manager;
+
+ private int changes = 0;
+
+ Set preCachedResources = new HashSet();
+
+ private boolean customLayoutArgumentsOpen = false;
+
+ private JsonTag tag;
+
+ private int errorsOpen;
+
+ private boolean cacheEnabled = false;
+
+ private Collection paintedComponents = new HashSet();
+
+ /**
+ * Creates a new XMLPrintWriter, without automatic line flushing.
+ *
+ * @param variableMap
+ * @param manager
+ * @param outWriter
+ * A character-output stream.
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ public JsonPaintTarget(CommunicationManager manager, PrintWriter outWriter,
+ boolean cachingRequired) throws PaintException {
+
+ this.manager = manager;
+
+ // Sets the target for UIDL writing
+ uidlBuffer = outWriter;
+
+ // Initialize tag-writing
+ mOpenTags = new Stack();
+ openJsonTags = new Stack();
+ cacheEnabled = cachingRequired;
+ }
+
+ public void startTag(String tagName) throws PaintException {
+ startTag(tagName, false);
+ }
+
+ /**
+ * Prints the element start tag.
+ *
+ * <pre>
+ * Todo:
+ * Checking of input values
+ *
+ * </pre>
+ *
+ * @param tagName
+ * the name of the start tag.
+ * @throws PaintException
+ * if the paint operation failed.
+ *
+ */
+ public void startTag(String tagName, boolean isChildNode)
+ throws PaintException {
+ // In case of null data output nothing:
+ if (tagName == null) {
+ throw new NullPointerException();
+ }
+
+ // Ensures that the target is open
+ if (closed) {
+ throw new PaintException(
+ "Attempted to write to a closed PaintTarget.");
+ }
+
+ if (tag != null) {
+ openJsonTags.push(tag);
+ }
+ // Checks tagName and attributes here
+ mOpenTags.push(tagName);
+
+ tag = new JsonTag(tagName);
+
+ customLayoutArgumentsOpen = "customlayout".equals(tagName);
+
+ if ("error".equals(tagName)) {
+ errorsOpen++;
+ }
+ }
+
+ /**
+ * Prints the element end tag.
+ *
+ * If the parent tag is closed before every child tag is closed an
+ * PaintException is raised.
+ *
+ * @param tag
+ * the name of the end tag.
+ * @throws Paintexception
+ * if the paint operation failed.
+ */
+ public void endTag(String tagName) throws PaintException {
+ // In case of null data output nothing:
+ if (tagName == null) {
+ throw new NullPointerException();
+ }
+
+ // Ensure that the target is open
+ if (closed) {
+ throw new PaintException(
+ "Attempted to write to a closed PaintTarget.");
+ }
+
+ if (openJsonTags.size() > 0) {
+ final JsonTag parent = (JsonTag) openJsonTags.pop();
+
+ String lastTag = "";
+
+ lastTag = (String) mOpenTags.pop();
+ if (!tagName.equalsIgnoreCase(lastTag)) {
+ throw new PaintException("Invalid UIDL: wrong ending tag: '"
+ + tagName + "' expected: '" + lastTag + "'.");
+ }
+
+ // simple hack which writes error uidl structure into attribute
+ if ("error".equals(lastTag)) {
+ if (errorsOpen == 1) {
+ parent.addAttribute("\"error\":[\"error\",{}"
+ + tag.getData() + "]");
+ } else {
+ // sub error
+ parent.addData(tag.getJSON());
+ }
+ errorsOpen--;
+ } else {
+ parent.addData(tag.getJSON());
+ }
+
+ tag = parent;
+ } else {
+ changes++;
+ uidlBuffer.print(((changes > 1) ? "," : "") + tag.getJSON());
+ tag = null;
+ }
+ }
+
+ /**
+ * Substitutes the XML sensitive characters with predefined XML entities.
+ *
+ * @param xml
+ * the String to be substituted.
+ * @return A new string instance where all occurrences of XML sensitive
+ * characters are substituted with entities.
+ */
+ static public String escapeXML(String xml) {
+ if (xml == null || xml.length() <= 0) {
+ return "";
+ }
+ return escapeXML(new StringBuffer(xml)).toString();
+ }
+
+ /**
+ * Substitutes the XML sensitive characters with predefined XML entities.
+ *
+ * @param xml
+ * the String to be substituted.
+ * @return A new StringBuffer instance where all occurrences of XML
+ * sensitive characters are substituted with entities.
+ *
+ */
+ static public StringBuffer escapeXML(StringBuffer xml) {
+ if (xml == null || xml.length() <= 0) {
+ return new StringBuffer("");
+ }
+
+ final StringBuffer result = new StringBuffer(xml.length() * 2);
+
+ for (int i = 0; i < xml.length(); i++) {
+ final char c = xml.charAt(i);
+ final String s = toXmlChar(c);
+ if (s != null) {
+ result.append(s);
+ } else {
+ result.append(c);
+ }
+ }
+ return result;
+ }
+
+ static public String escapeJSON(String s) {
+ if (s == null) {
+ return "";
+ }
+ final StringBuffer sb = new StringBuffer();
+ for (int i = 0; i < s.length(); i++) {
+ final char ch = s.charAt(i);
+ switch (ch) {
+ case '"':
+ sb.append("\\\"");
+ break;
+ case '\\':
+ sb.append("\\\\");
+ break;
+ case '\b':
+ sb.append("\\b");
+ break;
+ case '\f':
+ sb.append("\\f");
+ break;
+ case '\n':
+ sb.append("\\n");
+ break;
+ case '\r':
+ sb.append("\\r");
+ break;
+ case '\t':
+ sb.append("\\t");
+ break;
+ case '/':
+ sb.append("\\/");
+ break;
+ default:
+ if (ch >= '\u0000' && ch <= '\u001F') {
+ final String ss = Integer.toHexString(ch);
+ sb.append("\\u");
+ for (int k = 0; k < 4 - ss.length(); k++) {
+ sb.append('0');
+ }
+ sb.append(ss.toUpperCase());
+ } else {
+ sb.append(ch);
+ }
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Substitutes a XML sensitive character with predefined XML entity.
+ *
+ * @param c
+ * the Character to be replaced with an entity.
+ * @return String of the entity or null if character is not to be replaced
+ * with an entity.
+ */
+ private static String toXmlChar(char c) {
+ switch (c) {
+ case '&':
+ return "&"; // & => &
+ case '>':
+ return ">"; // > => >
+ case '<':
+ return "<"; // < => <
+ case '"':
+ return """; // " => "
+ case '\'':
+ return "'"; // ' => '
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * Prints XML-escaped text.
+ *
+ * @param str
+ * @throws PaintException
+ * if the paint operation failed.
+ *
+ */
+ public void addText(String str) throws PaintException {
+ tag.addData("\"" + escapeJSON(str) + "\"");
+ }
+
+ /**
+ * Adds a boolean attribute to component. Atributes must be added before any
+ * content is written.
+ *
+ * @param name
+ * the Attribute name.
+ * @param value
+ * the Attribute value.
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ public void addAttribute(String name, boolean value) throws PaintException {
+ tag.addAttribute("\"" + name + "\":" + (value ? "true" : "false"));
+ }
+
+ /**
+ * Adds a resource attribute to component. Attributes must be added before
+ * any content is written.
+ *
+ * @param name
+ * the Attribute name.
+ * @param value
+ * the Attribute value.
+ *
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ public void addAttribute(String name, Resource value) throws PaintException {
+
+ if (value instanceof ExternalResource) {
+ addAttribute(name, ((ExternalResource) value).getURL());
+
+ } else if (value instanceof ApplicationResource) {
+ final ApplicationResource r = (ApplicationResource) value;
+ final Application a = r.getApplication();
+ if (a == null) {
+ throw new PaintException(
+ "Application not specified for resorce "
+ + value.getClass().getName());
+ }
+ String uri;
+ if (a.getURL() != null) {
+ uri = a.getURL().getPath();
+ } else {
+ uri = "";
+ }
+ if (uri.length() > 0 && uri.charAt(uri.length() - 1) != '/') {
+ uri += "/";
+ }
+ uri += a.getRelativeLocation(r);
+ addAttribute(name, uri);
+
+ } else if (value instanceof ThemeResource) {
+ final String uri = "theme://"
+ + ((ThemeResource) value).getResourceId();
+ addAttribute(name, uri);
+ } else {
+ throw new PaintException("Ajax adapter does not "
+ + "support resources of type: "
+ + value.getClass().getName());
+ }
+
+ }
+
+ /**
+ * Adds a integer attribute to component. Atributes must be added before any
+ * content is written.
+ *
+ * @param name
+ * the Attribute name.
+ * @param value
+ * the Attribute value.
+ *
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ public void addAttribute(String name, int value) throws PaintException {
+ tag.addAttribute("\"" + name + "\":" + String.valueOf(value));
+ }
+
+ /**
+ * Adds a long attribute to component. Atributes must be added before any
+ * content is written.
+ *
+ * @param name
+ * the Attribute name.
+ * @param value
+ * the Attribute value.
+ *
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ public void addAttribute(String name, long value) throws PaintException {
+ tag.addAttribute("\"" + name + "\":" + String.valueOf(value));
+ }
+
+ /**
+ * Adds a float attribute to component. Atributes must be added before any
+ * content is written.
+ *
+ * @param name
+ * the Attribute name.
+ * @param value
+ * the Attribute value.
+ *
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ public void addAttribute(String name, float value) throws PaintException {
+ tag.addAttribute("\"" + name + "\":" + String.valueOf(value));
+ }
+
+ /**
+ * Adds a double attribute to component. Atributes must be added before any
+ * content is written.
+ *
+ * @param name
+ * the Attribute name.
+ * @param value
+ * the Attribute value.
+ *
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ public void addAttribute(String name, double value) throws PaintException {
+ tag.addAttribute("\"" + name + "\":" + String.valueOf(value));
+ }
+
+ /**
+ * Adds a string attribute to component. Atributes must be added before any
+ * content is written.
+ *
+ * @param name
+ * the String attribute name.
+ * @param value
+ * the String attribute value.
+ *
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ public void addAttribute(String name, String value) throws PaintException {
+ // In case of null data output nothing:
+ if ((value == null) || (name == null)) {
+ throw new NullPointerException(
+ "Parameters must be non-null strings");
+ }
+
+ tag.addAttribute("\"" + name + "\": \"" + escapeJSON(value) + "\"");
+
+ if (customLayoutArgumentsOpen && "template".equals(name)) {
+ getPreCachedResources().add("layouts/" + value + ".html");
+ }
+
+ if (name.equals("locale")) {
+ manager.requireLocale(value);
+ }
+
+ }
+
+ public void addAttribute(String name, Object[] values) {
+ // In case of null data output nothing:
+ if ((values == null) || (name == null)) {
+ throw new NullPointerException(
+ "Parameters must be non-null strings");
+ }
+ final StringBuffer buf = new StringBuffer();
+ buf.append("\"" + name + "\":[");
+ for (int i = 0; i < values.length; i++) {
+ if (i > 0) {
+ buf.append(",");
+ }
+ buf.append("\"");
+ buf.append(escapeJSON(values[i].toString()));
+ buf.append("\"");
+ }
+ buf.append("]");
+ tag.addAttribute(buf.toString());
+ }
+
+ /**
+ * Adds a string type variable.
+ *
+ * @param owner
+ * the Listener for variable changes.
+ * @param name
+ * the Variable name.
+ * @param value
+ * the Variable initial value.
+ *
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ public void addVariable(VariableOwner owner, String name, String value)
+ throws PaintException {
+ tag.addVariable(new StringVariable(owner, name, escapeJSON(value)));
+ }
+
+ /**
+ * Adds a int type variable.
+ *
+ * @param owner
+ * the Listener for variable changes.
+ * @param name
+ * the Variable name.
+ * @param value
+ * the Variable initial value.
+ *
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ public void addVariable(VariableOwner owner, String name, int value)
+ throws PaintException {
+ tag.addVariable(new IntVariable(owner, name, value));
+ }
+
+ /**
+ * Adds a long type variable.
+ *
+ * @param owner
+ * the Listener for variable changes.
+ * @param name
+ * the Variable name.
+ * @param value
+ * the Variable initial value.
+ *
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ public void addVariable(VariableOwner owner, String name, long value)
+ throws PaintException {
+ tag.addVariable(new LongVariable(owner, name, value));
+ }
+
+ /**
+ * Adds a float type variable.
+ *
+ * @param owner
+ * the Listener for variable changes.
+ * @param name
+ * the Variable name.
+ * @param value
+ * the Variable initial value.
+ *
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ public void addVariable(VariableOwner owner, String name, float value)
+ throws PaintException {
+ tag.addVariable(new FloatVariable(owner, name, value));
+ }
+
+ /**
+ * Adds a double type variable.
+ *
+ * @param owner
+ * the Listener for variable changes.
+ * @param name
+ * the Variable name.
+ * @param value
+ * the Variable initial value.
+ *
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ public void addVariable(VariableOwner owner, String name, double value)
+ throws PaintException {
+ tag.addVariable(new DoubleVariable(owner, name, value));
+ }
+
+ /**
+ * Adds a boolean type variable.
+ *
+ * @param owner
+ * the Listener for variable changes.
+ * @param name
+ * the Variable name.
+ * @param value
+ * the Variable initial value.
+ *
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ public void addVariable(VariableOwner owner, String name, boolean value)
+ throws PaintException {
+ tag.addVariable(new BooleanVariable(owner, name, value));
+ }
+
+ /**
+ * Adds a string array type variable.
+ *
+ * @param owner
+ * the Listener for variable changes.
+ * @param name
+ * the Variable name.
+ * @param value
+ * the Variable initial value.
+ *
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ public void addVariable(VariableOwner owner, String name, String[] value)
+ throws PaintException {
+ tag.addVariable(new ArrayVariable(owner, name, value));
+ }
+
+ /**
+ * Adds a upload stream type variable.
+ *
+ * TODO not converted for JSON
+ *
+ * @param owner
+ * the Listener for variable changes.
+ * @param name
+ * the Variable name.
+ *
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ public void addUploadStreamVariable(VariableOwner owner, String name)
+ throws PaintException {
+ startTag("uploadstream");
+ addAttribute(UIDL_ARG_NAME, name);
+ endTag("uploadstream");
+ }
+
+ /**
+ * Prints the single text section.
+ *
+ * Prints full text section. The section data is escaped
+ *
+ * @param sectionTagName
+ * the name of the tag.
+ * @param sectionData
+ * the section data to be printed.
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ public void addSection(String sectionTagName, String sectionData)
+ throws PaintException {
+ tag.addData("{\"" + sectionTagName + "\":\"" + escapeJSON(sectionData)
+ + "\"}");
+ }
+
+ /**
+ * Adds XML directly to UIDL.
+ *
+ * @param xml
+ * the Xml to be added.
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ public void addUIDL(String xml) throws PaintException {
+
+ // Ensure that the target is open
+ if (closed) {
+ throw new PaintException(
+ "Attempted to write to a closed PaintTarget.");
+ }
+
+ // Make sure that the open start tag is closed before
+ // anything is written.
+
+ // Escape and write what was given
+ if (xml != null) {
+ tag.addData("\"" + escapeJSON(xml) + "\"");
+ }
+
+ }
+
+ /**
+ * Adds XML section with namespace.
+ *
+ * @param sectionTagName
+ * the name of the tag.
+ * @param sectionData
+ * the section data.
+ * @param namespace
+ * the namespace to be added.
+ * @throws PaintException
+ * if the paint operation failed.
+ *
+ * @see com.itmill.toolkit.terminal.PaintTarget#addXMLSection(String,
+ * String, String)
+ */
+ public void addXMLSection(String sectionTagName, String sectionData,
+ String namespace) throws PaintException {
+
+ // Ensure that the target is open
+ if (closed) {
+ throw new PaintException(
+ "Attempted to write to a closed PaintTarget.");
+ }
+
+ startTag(sectionTagName);
+ if (namespace != null) {
+ addAttribute("xmlns", namespace);
+ }
+ customLayoutArgumentsOpen = false;
+
+ if (sectionData != null) {
+ tag.addData("\"" + escapeJSON(sectionData) + "\"");
+ }
+ endTag(sectionTagName);
+ }
+
+ /**
+ * Gets the UIDL already printed to stream. Paint target must be closed
+ * before the <code>getUIDL</code> can be called.
+ *
+ * @return the UIDL.
+ */
+ public String getUIDL() {
+ if (closed) {
+ return uidlBuffer.toString();
+ }
+ throw new IllegalStateException(
+ "Tried to read UIDL from open PaintTarget");
+ }
+
+ /**
+ * Closes the paint target. Paint target must be closed before the
+ * <code>getUIDL</code> can be called. Subsequent attempts to write to
+ * paint target. If the target was already closed, call to this function is
+ * ignored. will generate an exception.
+ *
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ public void close() throws PaintException {
+ if (tag != null) {
+ uidlBuffer.write(tag.getJSON());
+ }
+ flush();
+ closed = true;
+ }
+
+ /**
+ * Method flush.
+ */
+ private void flush() {
+ uidlBuffer.flush();
+ }
+
+ /**
+ * @see com.itmill.toolkit.terminal.PaintTarget#startTag(com.itmill.toolkit.terminal.Paintable,
+ * java.lang.String)
+ */
+ public boolean startTag(Paintable paintable, String tagName)
+ throws PaintException {
+ startTag(tagName, true);
+ final boolean isPreviouslyPainted = manager.hasPaintableId(paintable);
+ final String id = manager.getPaintableId(paintable);
+ paintable.addListener(manager);
+ addAttribute("id", id);
+ paintedComponents.add(paintable);
+ return cacheEnabled && isPreviouslyPainted;
+ }
+
+ public void paintReference(Paintable paintable, String referenceName)
+ throws PaintException {
+ final String id = manager.getPaintableId(paintable);
+ addAttribute(referenceName, id);
+ }
+
+ /**
+ * @see com.itmill.toolkit.terminal.PaintTarget#addCharacterData(java.lang.String)
+ */
+ public void addCharacterData(String text) throws PaintException {
+ if (text != null) {
+ tag.addData(text);
+ }
+ }
+
+ /**
+ * This is basically a container for UI components variables, that will be
+ * added at the end of JSON object.
+ *
+ * @author mattitahvonen
+ *
+ */
+ class JsonTag {
+ boolean firstField = false;
+
+ Vector variables = new Vector();
+
+ Vector children = new Vector();
+
+ Vector attr = new Vector();
+
+ StringBuffer data = new StringBuffer();
+
+ public boolean childrenArrayOpen = false;
+
+ private boolean childNode = false;
+
+ private boolean tagClosed = false;
+
+ public JsonTag(String tagName) {
+ data.append("[\"" + tagName + "\"");
+ }
+
+ private void closeTag() {
+ if (!tagClosed) {
+ data.append(attributesAsJsonObject());
+ data.append(getData());
+ // Writes the end (closing) tag
+ data.append("]");
+ tagClosed = true;
+ }
+ }
+
+ public String getJSON() {
+ if (!tagClosed) {
+ closeTag();
+ }
+ return data.toString();
+ }
+
+ public void openChildrenArray() {
+ if (!childrenArrayOpen) {
+ // append("c : [");
+ childrenArrayOpen = true;
+ // firstField = true;
+ }
+ }
+
+ public void closeChildrenArray() {
+ // append("]");
+ // firstField = false;
+ }
+
+ public void setChildNode(boolean b) {
+ childNode = b;
+ }
+
+ public boolean isChildNode() {
+ return childNode;
+ }
+
+ public String startField() {
+ if (firstField) {
+ firstField = false;
+ return "";
+ } else {
+ return ",";
+ }
+ }
+
+ /**
+ *
+ * @param s
+ * json string, object or array
+ */
+ public void addData(String s) {
+ children.add(s);
+ }
+
+ public String getData() {
+ final StringBuffer buf = new StringBuffer();
+ final Iterator it = children.iterator();
+ while (it.hasNext()) {
+ buf.append(startField());
+ buf.append(it.next());
+ }
+ return buf.toString();
+ }
+
+ public void addAttribute(String jsonNode) {
+ attr.add(jsonNode);
+ }
+
+ private String attributesAsJsonObject() {
+ final StringBuffer buf = new StringBuffer();
+ buf.append(startField());
+ buf.append("{");
+ for (final Iterator iter = attr.iterator(); iter.hasNext();) {
+ final String element = (String) iter.next();
+ buf.append(element);
+ if (iter.hasNext()) {
+ buf.append(",");
+ }
+ }
+ buf.append(tag.variablesAsJsonObject());
+ buf.append("}");
+ return buf.toString();
+ }
+
+ public void addVariable(Variable v) {
+ variables.add(v);
+ }
+
+ private String variablesAsJsonObject() {
+ if (variables.size() == 0) {
+ return "";
+ }
+ final StringBuffer buf = new StringBuffer();
+ buf.append(startField());
+ buf.append("\"v\":{");
+ final Iterator iter = variables.iterator();
+ while (iter.hasNext()) {
+ final Variable element = (Variable) iter.next();
+ buf.append(element.getJsonPresentation());
+ if (iter.hasNext()) {
+ buf.append(",");
+ }
+ }
+ buf.append("}");
+ return buf.toString();
+ }
+
+ class TagCounter {
+ int count;
+
+ public TagCounter() {
+ count = 0;
+ }
+
+ public void increment() {
+ count++;
+ }
+
+ public String postfix(String s) {
+ if (count > 0) {
+ return s + count;
+ }
+ return s;
+ }
+ }
+ }
+
+ abstract class Variable {
+
+ String name;
+
+ public abstract String getJsonPresentation();
+ }
+
+ class BooleanVariable extends Variable {
+ boolean value;
+
+ public BooleanVariable(VariableOwner owner, String name, boolean v) {
+ value = v;
+ this.name = name;
+ }
+
+ public String getJsonPresentation() {
+ return "\"" + name + "\":" + (value == true ? "true" : "false");
+ }
+
+ }
+
+ class StringVariable extends Variable {
+ String value;
+
+ public StringVariable(VariableOwner owner, String name, String v) {
+ value = v;
+ this.name = name;
+ }
+
+ public String getJsonPresentation() {
+ return "\"" + name + "\":\"" + value + "\"";
+ }
+
+ }
+
+ class IntVariable extends Variable {
+ int value;
+
+ public IntVariable(VariableOwner owner, String name, int v) {
+ value = v;
+ this.name = name;
+ }
+
+ public String getJsonPresentation() {
+ return "\"" + name + "\":" + value;
+ }
+ }
+
+ class LongVariable extends Variable {
+ long value;
+
+ public LongVariable(VariableOwner owner, String name, long v) {
+ value = v;
+ this.name = name;
+ }
+
+ public String getJsonPresentation() {
+ return "\"" + name + "\":" + value;
+ }
+ }
+
+ class FloatVariable extends Variable {
+ float value;
+
+ public FloatVariable(VariableOwner owner, String name, float v) {
+ value = v;
+ this.name = name;
+ }
+
+ public String getJsonPresentation() {
+ return "\"" + name + "\":" + value;
+ }
+ }
+
+ class DoubleVariable extends Variable {
+ double value;
+
+ public DoubleVariable(VariableOwner owner, String name, double v) {
+ value = v;
+ this.name = name;
+ }
+
+ public String getJsonPresentation() {
+ return "\"" + name + "\":" + value;
+ }
+ }
+
+ class ArrayVariable extends Variable {
+ String[] value;
+
+ public ArrayVariable(VariableOwner owner, String name, String[] v) {
+ value = v;
+ this.name = name;
+ }
+
+ public String getJsonPresentation() {
+ String pres = "\"" + name + "\":[";
+ for (int i = 0; i < value.length;) {
+ pres += "\"" + value[i] + "\"";
+ i++;
+ if (i < value.length) {
+ pres += ",";
+ }
+ }
+ pres += "]";
+ return pres;
+ }
+ }
+
+ public Set getPreCachedResources() {
+ return preCachedResources;
+ }
+
+ public void setPreCachedResources(Set preCachedResources) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Method to check if paintable is already painted into this target.
+ *
+ * @param p
+ * @return true if is not yet painted into this target and is connected to
+ * app
+ */
+ public boolean needsToBePainted(Paintable p) {
+ if (paintedComponents.contains(p)) {
+ return false;
+ } else if (((Component) p).getApplication() == null) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+}
--- /dev/null
+/**\r
+ * \r
+ */\r
+package com.itmill.toolkit.terminal.gwt.server;\r
+\r
+import java.io.IOException;\r
+import java.io.OutputStream;\r
+import java.io.PrintWriter;\r
+import java.util.HashMap;\r
+import java.util.Iterator;\r
+import java.util.LinkedHashSet;\r
+import java.util.Locale;\r
+import java.util.Map;\r
+import java.util.Set;\r
+\r
+import javax.portlet.ActionRequest;\r
+import javax.portlet.ActionResponse;\r
+import javax.portlet.Portlet;\r
+import javax.portlet.PortletSession;\r
+import javax.portlet.PortletURL;\r
+import javax.portlet.RenderRequest;\r
+import javax.portlet.RenderResponse;\r
+import javax.servlet.http.HttpSession;\r
+\r
+import com.itmill.toolkit.Application;\r
+\r
+/**\r
+ * @author marc\r
+ * \r
+ */\r
+public class PortletApplicationContext extends WebApplicationContext {\r
+\r
+ protected PortletSession portletSession;\r
+\r
+ protected Map portletListeners = new HashMap();\r
+\r
+ protected Map portletToApplication = new HashMap();\r
+\r
+ PortletApplicationContext() {\r
+\r
+ }\r
+\r
+ static public PortletApplicationContext getApplicationContext(\r
+ PortletSession session) {\r
+ WebApplicationContext cx = (WebApplicationContext) session\r
+ .getAttribute(WebApplicationContext.class.getName(),\r
+ PortletSession.APPLICATION_SCOPE);\r
+ if (cx == null) {\r
+ cx = new PortletApplicationContext();\r
+ }\r
+ if (!(cx instanceof PortletApplicationContext)) {\r
+ // TODO Should we even try this? And should we leave original as-is?\r
+ PortletApplicationContext pcx = new PortletApplicationContext();\r
+ pcx.applications.addAll(cx.applications);\r
+ cx.applications.clear();\r
+ pcx.browser = cx.browser;\r
+ cx.browser = null;\r
+ pcx.listeners = cx.listeners;\r
+ cx.listeners = null;\r
+ pcx.session = cx.session;\r
+ cx = pcx;\r
+ }\r
+ if (((PortletApplicationContext) cx).portletSession == null) {\r
+ ((PortletApplicationContext) cx).portletSession = session;\r
+ }\r
+ session.setAttribute(WebApplicationContext.class.getName(), cx,\r
+ PortletSession.APPLICATION_SCOPE);\r
+ return (PortletApplicationContext) cx;\r
+ }\r
+\r
+ static public WebApplicationContext getApplicationContext(\r
+ HttpSession session) {\r
+ WebApplicationContext cx = (WebApplicationContext) session\r
+ .getAttribute(WebApplicationContext.class.getName());\r
+ if (cx == null) {\r
+ cx = new PortletApplicationContext();\r
+ }\r
+ if (cx.session == null) {\r
+ cx.session = session;\r
+ }\r
+ session.setAttribute(WebApplicationContext.class.getName(), cx);\r
+ return cx;\r
+ }\r
+\r
+ protected void removeApplication(Application application) {\r
+ portletListeners.remove(application);\r
+ for (Iterator it = portletToApplication.keySet().iterator(); it\r
+ .hasNext();) {\r
+ Object key = it.next();\r
+ if (key == application) {\r
+ portletToApplication.remove(key);\r
+ }\r
+ }\r
+ super.removeApplication(application);\r
+ }\r
+\r
+ public boolean equals(Object obj) {\r
+ if (portletSession == null) {\r
+ return super.equals(obj);\r
+ }\r
+ return portletSession.equals(obj);\r
+ }\r
+\r
+ public int hashCode() {\r
+ if (portletSession == null) {\r
+ return super.hashCode();\r
+ }\r
+ return portletSession.hashCode();\r
+ }\r
+\r
+ public void setPortletApplication(Portlet portlet, Application app) {\r
+ portletToApplication.put(portlet, app);\r
+ }\r
+\r
+ public Application getPortletApplication(Portlet portlet) {\r
+ return (Application) portletToApplication.get(portlet);\r
+ }\r
+\r
+ public PortletSession getPortletSession() {\r
+ return portletSession;\r
+ }\r
+\r
+ public void addPortletListener(Application app, PortletListener listener) {\r
+ Set l = (Set) portletListeners.get(app);\r
+ if (l == null) {\r
+ l = new LinkedHashSet();\r
+ portletListeners.put(app, l);\r
+ }\r
+ l.add(listener);\r
+ }\r
+\r
+ public void removePortletListener(Application app, PortletListener listener) {\r
+ Set l = (Set) portletListeners.get(app);\r
+ if (l != null) {\r
+ l.remove(listener);\r
+ }\r
+ }\r
+\r
+ public static void dispatchRequest(Portlet portlet, RenderRequest request,\r
+ RenderResponse response) {\r
+ PortletApplicationContext ctx = getApplicationContext(request\r
+ .getPortletSession());\r
+ if (ctx != null) {\r
+ ctx.firePortletRenderRequest(portlet, request, response);\r
+ }\r
+ }\r
+\r
+ public static void dispatchRequest(Portlet portlet, ActionRequest request,\r
+ ActionResponse response) {\r
+ PortletApplicationContext ctx = getApplicationContext(request\r
+ .getPortletSession());\r
+ if (ctx != null) {\r
+ ctx.firePortletActionRequest(portlet, request, response);\r
+ }\r
+ }\r
+\r
+ public void firePortletRenderRequest(Portlet portlet,\r
+ RenderRequest request, RenderResponse response) {\r
+ Application app = getPortletApplication(portlet);\r
+ Set listeners = (Set) portletListeners.get(app);\r
+ if (listeners != null) {\r
+ for (Iterator it = listeners.iterator(); it.hasNext();) {\r
+ PortletListener l = (PortletListener) it.next();\r
+ l.handleRenderRequest(request, new RestrictedRenderResponse(\r
+ response));\r
+ }\r
+ }\r
+ }\r
+\r
+ public void firePortletActionRequest(Portlet portlet,\r
+ ActionRequest request, ActionResponse response) {\r
+ Application app = getPortletApplication(portlet);\r
+ Set listeners = (Set) portletListeners.get(app);\r
+ if (listeners != null) {\r
+ for (Iterator it = listeners.iterator(); it.hasNext();) {\r
+ PortletListener l = (PortletListener) it.next();\r
+ l.handleActionRequest(request, response);\r
+ }\r
+ }\r
+ }\r
+\r
+ public interface PortletListener {\r
+ public void handleRenderRequest(RenderRequest request,\r
+ RenderResponse response);\r
+\r
+ public void handleActionRequest(ActionRequest request,\r
+ ActionResponse response);\r
+ }\r
+\r
+ private class RestrictedRenderResponse implements RenderResponse {\r
+\r
+ private RenderResponse response;\r
+\r
+ private RestrictedRenderResponse(RenderResponse response) {\r
+ this.response = response;\r
+ }\r
+\r
+ public void addProperty(String key, String value) {\r
+ response.addProperty(key, value);\r
+ }\r
+\r
+ public PortletURL createActionURL() {\r
+ return response.createActionURL();\r
+ }\r
+\r
+ public PortletURL createRenderURL() {\r
+ return response.createRenderURL();\r
+ }\r
+\r
+ public String encodeURL(String path) {\r
+ return response.encodeURL(path);\r
+ }\r
+\r
+ public void flushBuffer() throws IOException {\r
+ // NOP\r
+ // TODO throw?\r
+ }\r
+\r
+ public int getBufferSize() {\r
+ return response.getBufferSize();\r
+ }\r
+\r
+ public String getCharacterEncoding() {\r
+ return response.getCharacterEncoding();\r
+ }\r
+\r
+ public String getContentType() {\r
+ return response.getContentType();\r
+ }\r
+\r
+ public Locale getLocale() {\r
+ return response.getLocale();\r
+ }\r
+\r
+ public String getNamespace() {\r
+ return response.getNamespace();\r
+ }\r
+\r
+ public OutputStream getPortletOutputStream() throws IOException {\r
+ // write forbidden\r
+ return null;\r
+ }\r
+\r
+ public PrintWriter getWriter() throws IOException {\r
+ // write forbidden\r
+ return null;\r
+ }\r
+\r
+ public boolean isCommitted() {\r
+ return response.isCommitted();\r
+ }\r
+\r
+ public void reset() {\r
+ // NOP\r
+ // TODO throw?\r
+ }\r
+\r
+ public void resetBuffer() {\r
+ // NOP\r
+ // TODO throw?\r
+ }\r
+\r
+ public void setBufferSize(int size) {\r
+ // NOP\r
+ // TODO throw?\r
+ }\r
+\r
+ public void setContentType(String type) {\r
+ // NOP\r
+ // TODO throw?\r
+ }\r
+\r
+ public void setProperty(String key, String value) {\r
+ response.setProperty(key, value);\r
+ }\r
+\r
+ public void setTitle(String title) {\r
+ response.setTitle(title);\r
+ }\r
+\r
+ }\r
+\r
+}\r
--- /dev/null
+package com.itmill.toolkit.terminal.gwt.server;
+
+public class SessionExpired extends Exception {
+
+ private static final long serialVersionUID = -2211425033877155423L;
+
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.server;
+
+import java.io.File;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpSessionBindingEvent;
+import javax.servlet.http.HttpSessionBindingListener;
+
+import com.itmill.toolkit.Application;
+import com.itmill.toolkit.service.ApplicationContext;
+
+/**
+ * Web application context for the IT Mill Toolkit applications.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.1
+ */
+public class WebApplicationContext implements ApplicationContext,
+ HttpSessionBindingListener {
+
+ protected List listeners;
+
+ protected HttpSession session;
+
+ protected final HashSet applications = new HashSet();
+
+ protected WebBrowser browser = new WebBrowser();
+
+ /**
+ * Creates a new Web Application Context.
+ *
+ */
+ WebApplicationContext() {
+
+ }
+
+ /**
+ * Gets the application context base directory.
+ *
+ * @see com.itmill.toolkit.service.ApplicationContext#getBaseDirectory()
+ */
+ public File getBaseDirectory() {
+ final String realPath = ApplicationServlet.getResourcePath(session
+ .getServletContext(), "/");
+ if (realPath == null) {
+ return null;
+ }
+ return new File(realPath);
+ }
+
+ /**
+ * Gets the http-session application is running in.
+ *
+ * @return HttpSession this application context resides in.
+ */
+ public HttpSession getHttpSession() {
+ return session;
+ }
+
+ /**
+ * Gets the applications in this context.
+ *
+ * @see com.itmill.toolkit.service.ApplicationContext#getApplications()
+ */
+ public Collection getApplications() {
+ return Collections.unmodifiableCollection(applications);
+ }
+
+ /**
+ * Gets the application context for HttpSession.
+ *
+ * @param session
+ * the HTTP session.
+ * @return the application context for HttpSession.
+ */
+ static public WebApplicationContext getApplicationContext(
+ HttpSession session) {
+ WebApplicationContext cx = (WebApplicationContext) session
+ .getAttribute(WebApplicationContext.class.getName());
+ if (cx == null) {
+ cx = new WebApplicationContext();
+ session.setAttribute(WebApplicationContext.class.getName(), cx);
+ }
+ if (cx.session == null) {
+ cx.session = session;
+ }
+ return cx;
+ }
+
+ /**
+ * Returns <code>true</code> if and only if the argument is not
+ * <code>null</code> and is a Boolean object that represents the same
+ * boolean value as this object.
+ *
+ * @param obj
+ * the object to compare with.
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ public boolean equals(Object obj) {
+ return session.equals(obj);
+ }
+
+ /**
+ * Returns the hash code value .
+ *
+ * @see java.lang.Object#hashCode()
+ */
+ public int hashCode() {
+ return session.hashCode();
+ }
+
+ /**
+ * Adds the transaction listener to this context.
+ *
+ * @see com.itmill.toolkit.service.ApplicationContext#addTransactionListener(com.itmill.toolkit.service.ApplicationContext.TransactionListener)
+ */
+ public void addTransactionListener(TransactionListener listener) {
+ if (listeners == null) {
+ listeners = new LinkedList();
+ }
+ listeners.add(listener);
+ }
+
+ /**
+ * Removes the transaction listener from this context.
+ *
+ * @see com.itmill.toolkit.service.ApplicationContext#removeTransactionListener(com.itmill.toolkit.service.ApplicationContext.TransactionListener)
+ */
+ public void removeTransactionListener(TransactionListener listener) {
+ if (listeners != null) {
+ listeners.remove(listener);
+ }
+
+ }
+
+ /**
+ * Notifies the transaction start.
+ *
+ * @param application
+ * @param request
+ * the HTTP request.
+ */
+ protected void startTransaction(Application application,
+ HttpServletRequest request) {
+ if (listeners == null) {
+ return;
+ }
+ for (final Iterator i = listeners.iterator(); i.hasNext();) {
+ ((ApplicationContext.TransactionListener) i.next())
+ .transactionStart(application, request);
+ }
+ }
+
+ /**
+ * Notifies the transaction end.
+ *
+ * @param application
+ * @param request
+ * the HTTP request.
+ */
+ protected void endTransaction(Application application,
+ HttpServletRequest request) {
+ if (listeners == null) {
+ return;
+ }
+
+ LinkedList exceptions = null;
+ for (final Iterator i = listeners.iterator(); i.hasNext();) {
+ try {
+ ((ApplicationContext.TransactionListener) i.next())
+ .transactionEnd(application, request);
+ } catch (final RuntimeException t) {
+ if (exceptions == null) {
+ exceptions = new LinkedList();
+ }
+ exceptions.add(t);
+ }
+ }
+
+ // If any runtime exceptions occurred, throw a combined exception
+ if (exceptions != null) {
+ final StringBuffer msg = new StringBuffer();
+ for (final Iterator i = exceptions.iterator(); i.hasNext();) {
+ final RuntimeException e = (RuntimeException) i.next();
+ if (msg.length() == 0) {
+ msg.append("\n\n--------------------------\n\n");
+ }
+ msg.append(e.getMessage() + "\n");
+ final StringWriter trace = new StringWriter();
+ e.printStackTrace(new PrintWriter(trace, true));
+ msg.append(trace.toString());
+ }
+ throw new RuntimeException(msg.toString());
+ }
+ }
+
+ protected void removeApplication(Application application) {
+ applications.remove(application);
+ }
+
+ protected void addApplication(Application application) {
+ applications.add(application);
+ }
+
+ /**
+ * @see javax.servlet.http.HttpSessionBindingListener#valueBound(HttpSessionBindingEvent)
+ */
+ public void valueBound(HttpSessionBindingEvent arg0) {
+ // We are not interested in bindings
+ }
+
+ /**
+ * @see javax.servlet.http.HttpSessionBindingListener#valueUnbound(HttpSessionBindingEvent)
+ */
+ public void valueUnbound(HttpSessionBindingEvent event) {
+ // If we are going to be unbound from the session, the session must be
+ // closing
+ try {
+ while (!applications.isEmpty()) {
+ final Application app = (Application) applications.iterator()
+ .next();
+ app.close();
+ ApplicationServlet.applicationToAjaxAppMgrMap.remove(app);
+ removeApplication(app);
+ }
+ } catch (Exception e) {
+ // This should never happen but is possible with rare
+ // configurations (e.g. robustness tests). If you have one
+ // thread doing HTTP socket write and another thread trying to
+ // remove same application here. Possible if you got e.g. session
+ // lifetime 1 min but socket write may take longer than 1 min.
+ System.err.println("Could not remove application, leaking memory.");
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Get the web browser associated with this application context.
+ *
+ * Because application context is related to the http session and server
+ * maintains one session per browser-instance, each context has exactly one
+ * web browser associated with it.
+ *
+ * @return
+ */
+ public WebBrowser getBrowser() {
+ return browser;
+ }
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.server;
+
+import java.util.Locale;
+
+import javax.servlet.http.HttpServletRequest;
+
+import com.itmill.toolkit.terminal.Terminal;
+
+public class WebBrowser implements Terminal {
+
+ private int screenHeight = 0;
+ private int screenWidth = 0;
+ private String browserApplication = null;
+ private Locale locale;
+ private String address;
+ private boolean secureConnection;
+
+ /**
+ * There is no default-theme for this terminal type.
+ *
+ * @return Allways returns null.
+ */
+ public String getDefaultTheme() {
+ return null;
+ }
+
+ /**
+ * Get the height of the users display in pixels.
+ *
+ */
+ public int getScreenHeight() {
+ return screenHeight;
+ }
+
+ /**
+ * Get the width of the users display in pixels.
+ *
+ */
+ public int getScreenWidth() {
+ return screenWidth;
+ }
+
+ /**
+ * Get the browser user-agent string.
+ *
+ * @return
+ */
+ public String getBrowserApplication() {
+ return browserApplication;
+ }
+
+ void updateBrowserProperties(HttpServletRequest request) {
+ locale = request.getLocale();
+ address = request.getRemoteAddr();
+ secureConnection = request.isSecure();
+
+ final String agent = request.getHeader("user-agent");
+ if (agent != null) {
+ browserApplication = agent;
+ }
+
+ final String sw = request.getParameter("screenWidth");
+ final String sh = request.getParameter("screenHeight");
+ if (sw != null && sh != null) {
+ try {
+ screenHeight = Integer.parseInt(sh);
+ screenWidth = Integer.parseInt(sw);
+ } catch (final NumberFormatException e) {
+ screenHeight = screenWidth = 0;
+ }
+ }
+ }
+
+ /**
+ * Get the IP-address of the web browser.
+ *
+ * @return IP-address in 1.12.123.123 -format
+ */
+ public String getAddress() {
+ return address;
+ }
+
+ /** Get the default locate of the browser. */
+ public Locale getLocale() {
+ return locale;
+ }
+
+ /** Is the connection made using HTTPS? */
+ public boolean isSecureConnection() {
+ return secureConnection;
+ }
+
+}
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+
+<body bgcolor="white">
+
+<!-- Package summary here -->
+
+<p>Provides classes and interfaces that wrap the terminal-side functionalities
+for the server-side application. (FIXME: This could be a little more descriptive and wordy.)</p>
+
+<h2>Package Specification</h2>
+
+<!-- Package spec here -->
+
+<!-- Put @see and @since tags down here. -->
+
+</body>
+</html>
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.web;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+
+/**
+ * This package and servlet is only provided for backward compatibility. Since
+ * Toolkit version 5.0 you should use
+ * com.itmill.toolkit.terminal.gwt.server.ApplicationServlet instead of this
+ * class.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 5.0
+ */
+public class ApplicationServlet extends
+ com.itmill.toolkit.terminal.gwt.server.ApplicationServlet {
+
+ private static final long serialVersionUID = -1471357707917217303L;
+
+ public void init(ServletConfig servletConfig) throws ServletException {
+ System.err
+ .println("Compatiblity class in use. Please use com.itmill.toolkit.terminal.gwt.server.ApplicationServlet instead. You probably need to update your web.xml.");
+ super.init(servletConfig);
+ }
+
+}