diff options
Diffstat (limited to 'src')
204 files changed, 8644 insertions, 3952 deletions
diff --git a/src/com/vaadin/Application.java b/src/com/vaadin/Application.java index 4da1d52c00..84a8df5053 100644 --- a/src/com/vaadin/Application.java +++ b/src/com/vaadin/Application.java @@ -18,7 +18,6 @@ import java.util.EventObject; import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; -import java.util.Iterator; import java.util.LinkedList; import java.util.Locale; import java.util.Map; @@ -48,14 +47,12 @@ import com.vaadin.terminal.WrappedRequest; import com.vaadin.terminal.WrappedRequest.BrowserDetails; import com.vaadin.terminal.WrappedResponse; import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.Connector; import com.vaadin.terminal.gwt.server.AbstractApplicationServlet; import com.vaadin.terminal.gwt.server.ChangeVariablesErrorEvent; import com.vaadin.terminal.gwt.server.ClientConnector; import com.vaadin.terminal.gwt.server.WebApplicationContext; import com.vaadin.ui.AbstractComponent; import com.vaadin.ui.AbstractField; -import com.vaadin.ui.Component; import com.vaadin.ui.Root; import com.vaadin.ui.Table; import com.vaadin.ui.Window; @@ -194,6 +191,7 @@ public class Application implements Terminal.ErrorListener, Serializable { * @see #getWindow(String) * @see Application#getRoot(WrappedRequest) */ + @Override public Root.LegacyWindow getRoot(WrappedRequest request) { String pathInfo = request.getRequestPathInfo(); @@ -246,6 +244,7 @@ public class Application implements Terminal.ErrorListener, Serializable { * <p> * {@inheritDoc} */ + @Override public String getThemeForRoot(Root root) { return theme; @@ -1066,12 +1065,14 @@ public class Application implements Terminal.ErrorListener, Serializable { * the change event. * @see com.vaadin.terminal.Terminal.ErrorListener#terminalError(com.vaadin.terminal.Terminal.ErrorEvent) */ + public void terminalError(Terminal.ErrorEvent event) { final Throwable t = event.getThrowable(); if (t instanceof SocketException) { // Most likely client browser closed socket - logger.info("SocketException in CommunicationManager." - + " Most likely client (browser) closed socket."); + getLogger().info( + "SocketException in CommunicationManager." + + " Most likely client (browser) closed socket."); return; } @@ -1090,7 +1091,7 @@ public class Application implements Terminal.ErrorListener, Serializable { } // also print the error on console - logger.log(Level.SEVERE, "Terminal error:", t); + getLogger().log(Level.SEVERE, "Terminal error:", t); } /** @@ -2118,11 +2119,11 @@ public class Application implements Terminal.ErrorListener, Serializable { * @return the current application instance if available, otherwise * <code>null</code> * - * @see #setCurrentApplication(Application) + * @see #setCurrent(Application) * * @since 7.0 */ - public static Application getCurrentApplication() { + public static Application getCurrent() { return currentApplication.get(); } @@ -2138,12 +2139,12 @@ public class Application implements Terminal.ErrorListener, Serializable { * * @param application * - * @see #getCurrentApplication() + * @see #getCurrent() * @see ThreadLocal * * @since 7.0 */ - public static void setCurrentApplication(Application application) { + public static void setCurrent(Application application) { currentApplication.set(application); } @@ -2187,7 +2188,7 @@ public class Application implements Terminal.ErrorListener, Serializable { */ public Root getRootForRequest(WrappedRequest request) throws RootRequiresMoreInformationException { - Root root = Root.getCurrentRoot(); + Root root = Root.getCurrent(); if (root != null) { return root; } @@ -2239,7 +2240,7 @@ public class Application implements Terminal.ErrorListener, Serializable { } // Set thread local here so it is available in init - Root.setCurrentRoot(root); + Root.setCurrent(root); if (!initedRoots.contains(rootId)) { boolean initRequiresBrowserDetails = isRootPreserved() @@ -2355,8 +2356,6 @@ public class Application implements Terminal.ErrorListener, Serializable { return Collections.unmodifiableCollection(roots.values()); } - private final HashMap<String, ClientConnector> connectorIdToConnector = new HashMap<String, ClientConnector>(); - private int connectorIdSequence = 0; /** @@ -2368,52 +2367,10 @@ public class Application implements Terminal.ErrorListener, Serializable { * @return A new id for the connector */ public String createConnectorId(ClientConnector connector) { - String connectorId = String.valueOf(connectorIdSequence++); - Connector oldReference = connectorIdToConnector.put(connectorId, - connector); - if (oldReference != null) { - throw new RuntimeException( - "An error occured while generating connector ids. A connector with id " - + connectorId + " was already found!"); - } - return connectorId; - } - - /** - * Gets a connector by its id. - * - * @param connectorId - * The connector id to look for - * @return The connector with the given id or null if no connector has the - * given id - */ - public ClientConnector getConnector(String connectorId) { - return connectorIdToConnector.get(connectorId); + return String.valueOf(connectorIdSequence++); } - /** - * Cleans the connector map from all connectors that are no longer attached - * to the application. This should only be called by the framework. - */ - public void cleanConnectorMap() { - // remove detached components from paintableIdMap so they - // can be GC'ed - Iterator<String> iterator = connectorIdToConnector.keySet().iterator(); - - while (iterator.hasNext()) { - String connectorId = iterator.next(); - Connector connector = connectorIdToConnector.get(connectorId); - if (connector instanceof Component) { - Component component = (Component) connector; - if (component.getApplication() != this) { - // If component is no longer part of this application, - // remove it from the map. If it is re-attached to the - // application at some point it will be re-added to this - // collection when sent to the client. - iterator.remove(); - } - } - } - + private static final Logger getLogger() { + return Logger.getLogger(Application.class.getName()); } } diff --git a/src/com/vaadin/annotations/LoadScripts.java b/src/com/vaadin/annotations/LoadScripts.java new file mode 100644 index 0000000000..84ac2d2fb7 --- /dev/null +++ b/src/com/vaadin/annotations/LoadScripts.java @@ -0,0 +1,24 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Temporary hack used for ensuring external javascript libraries are included. + * To add a javascript, add this annotation to your Root class. + * + * @deprecated Will be removed in favor of a more robust solution before version + * 7.0.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Deprecated +public @interface LoadScripts { + public String[] value(); + +} diff --git a/src/com/vaadin/data/util/AbstractBeanContainer.java b/src/com/vaadin/data/util/AbstractBeanContainer.java index bed3ca0450..300f92ea3a 100644 --- a/src/com/vaadin/data/util/AbstractBeanContainer.java +++ b/src/com/vaadin/data/util/AbstractBeanContainer.java @@ -8,6 +8,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import com.vaadin.data.Container; @@ -247,8 +248,8 @@ public abstract class AbstractBeanContainer<IDTYPE, BEANTYPE> extends */ @Override @SuppressWarnings("unchecked") - public Collection<IDTYPE> getItemIds() { - return (Collection<IDTYPE>) super.getItemIds(); + public List<IDTYPE> getItemIds() { + return (List<IDTYPE>) super.getItemIds(); } /* diff --git a/src/com/vaadin/data/util/AbstractInMemoryContainer.java b/src/com/vaadin/data/util/AbstractInMemoryContainer.java index cc7f469c0c..795aabb419 100644 --- a/src/com/vaadin/data/util/AbstractInMemoryContainer.java +++ b/src/com/vaadin/data/util/AbstractInMemoryContainer.java @@ -165,8 +165,8 @@ public abstract class AbstractInMemoryContainer<ITEMIDTYPE, PROPERTYIDCLASS, ITE } } - public Collection<?> getItemIds() { - return Collections.unmodifiableCollection(getVisibleItemIds()); + public List<?> getItemIds() { + return Collections.unmodifiableList(getVisibleItemIds()); } // Container.Ordered diff --git a/src/com/vaadin/data/util/MethodProperty.java b/src/com/vaadin/data/util/MethodProperty.java index 4fc5531320..7df45e04ab 100644 --- a/src/com/vaadin/data/util/MethodProperty.java +++ b/src/com/vaadin/data/util/MethodProperty.java @@ -48,8 +48,6 @@ import com.vaadin.util.SerializerHelper; @SuppressWarnings("serial") public class MethodProperty<T> extends AbstractProperty<T> { - private static final Logger logger = Logger.getLogger(MethodProperty.class - .getName()); /** * The object that includes the property the MethodProperty is bound to. */ @@ -130,9 +128,9 @@ public class MethodProperty<T> extends AbstractProperty<T> { getMethod = null; } } catch (SecurityException e) { - logger.log(Level.SEVERE, "Internal deserialization error", e); + getLogger().log(Level.SEVERE, "Internal deserialization error", e); } catch (NoSuchMethodException e) { - logger.log(Level.SEVERE, "Internal deserialization error", e); + getLogger().log(Level.SEVERE, "Internal deserialization error", e); } }; @@ -777,4 +775,7 @@ public class MethodProperty<T> extends AbstractProperty<T> { super.fireValueChange(); } + private static final Logger getLogger() { + return Logger.getLogger(MethodProperty.class.getName()); + } } diff --git a/src/com/vaadin/data/util/MethodPropertyDescriptor.java b/src/com/vaadin/data/util/MethodPropertyDescriptor.java index 10faa7a0f3..5fdb982b9e 100644 --- a/src/com/vaadin/data/util/MethodPropertyDescriptor.java +++ b/src/com/vaadin/data/util/MethodPropertyDescriptor.java @@ -23,9 +23,6 @@ import com.vaadin.util.SerializerHelper; public class MethodPropertyDescriptor<BT> implements VaadinPropertyDescriptor<BT> { - private static final Logger logger = Logger - .getLogger(MethodPropertyDescriptor.class.getName()); - private final String name; private Class<?> propertyType; private transient Method readMethod; @@ -109,9 +106,9 @@ public class MethodPropertyDescriptor<BT> implements readMethod = null; } } catch (SecurityException e) { - logger.log(Level.SEVERE, "Internal deserialization error", e); + getLogger().log(Level.SEVERE, "Internal deserialization error", e); } catch (NoSuchMethodException e) { - logger.log(Level.SEVERE, "Internal deserialization error", e); + getLogger().log(Level.SEVERE, "Internal deserialization error", e); } }; @@ -128,4 +125,7 @@ public class MethodPropertyDescriptor<BT> implements writeMethod); } -} + private static final Logger getLogger() { + return Logger.getLogger(MethodPropertyDescriptor.class.getName()); + } +}
\ No newline at end of file diff --git a/src/com/vaadin/data/util/converter/ConverterUtil.java b/src/com/vaadin/data/util/converter/ConverterUtil.java new file mode 100644 index 0000000000..7011496ed7 --- /dev/null +++ b/src/com/vaadin/data/util/converter/ConverterUtil.java @@ -0,0 +1,168 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.data.util.converter; + +import java.io.Serializable; +import java.util.Locale; + +import com.vaadin.Application; + +public class ConverterUtil implements Serializable { + + /** + * Finds a converter that can convert from the given presentation type to + * the given model type and back. Uses the given application to find a + * {@link ConverterFactory} or, if application is null, uses the + * {@link Application#getCurrent()}. + * + * @param <PRESENTATIONTYPE> + * The presentation type + * @param <MODELTYPE> + * The model type + * @param presentationType + * The presentation type + * @param modelType + * The model type + * @param application + * The application to use to find a ConverterFactory or null to + * use the current application + * @return A Converter capable of converting between the given types or null + * if no converter was found + */ + public static <PRESENTATIONTYPE, MODELTYPE> Converter<PRESENTATIONTYPE, MODELTYPE> getConverter( + Class<PRESENTATIONTYPE> presentationType, + Class<MODELTYPE> modelType, Application application) { + Converter<PRESENTATIONTYPE, MODELTYPE> converter = null; + if (application == null) { + application = Application.getCurrent(); + } + + if (application != null) { + ConverterFactory factory = application.getConverterFactory(); + converter = factory.createConverter(presentationType, modelType); + } + return converter; + + } + + /** + * Convert the given value from the data source type to the UI type. + * + * @param modelValue + * The model value to convert + * @param presentationType + * The type of the presentation value + * @param converter + * The converter to (try to) use + * @param locale + * The locale to use for conversion + * @param <PRESENTATIONTYPE> + * Presentation type + * + * @return The converted value, compatible with the presentation type, or + * the original value if its type is compatible and no converter is + * set. + * @throws Converter.ConversionException + * if there is no converter and the type is not compatible with + * the model type. + */ + @SuppressWarnings("unchecked") + public static <PRESENTATIONTYPE, MODELTYPE> PRESENTATIONTYPE convertFromModel( + MODELTYPE modelValue, + Class<? extends PRESENTATIONTYPE> presentationType, + Converter<PRESENTATIONTYPE, MODELTYPE> converter, Locale locale) + throws Converter.ConversionException { + if (converter != null) { + return converter.convertToPresentation(modelValue, locale); + } + if (modelValue == null) { + return null; + } + + if (presentationType.isAssignableFrom(modelValue.getClass())) { + return (PRESENTATIONTYPE) modelValue; + } else { + throw new Converter.ConversionException( + "Unable to convert value of type " + + modelValue.getClass().getName() + + " to presentation type " + + presentationType + + ". No converter is set and the types are not compatible."); + } + } + + /** + * @param <MODELTYPE> + * @param <PRESENTATIONTYPE> + * @param presentationValue + * @param modelType + * @param converter + * @param locale + * @return + * @throws Converter.ConversionException + */ + public static <MODELTYPE, PRESENTATIONTYPE> MODELTYPE convertToModel( + PRESENTATIONTYPE presentationValue, Class<MODELTYPE> modelType, + Converter<PRESENTATIONTYPE, MODELTYPE> converter, Locale locale) + throws Converter.ConversionException { + if (converter != null) { + /* + * If there is a converter, always use it. It must convert or throw + * an exception. + */ + return converter.convertToModel(presentationValue, locale); + } + + if (presentationValue == null) { + // Null should always be passed through the converter but if there + // is no converter we can safely return null + return null; + } + + if (modelType == null) { + // No model type, return original value + return (MODELTYPE) presentationValue; + } else if (modelType.isAssignableFrom(presentationValue.getClass())) { + // presentation type directly compatible with model type + return modelType.cast(presentationValue); + } else { + throw new Converter.ConversionException( + "Unable to convert value of type " + + presentationValue.getClass().getName() + + " to model type " + + modelType + + ". No converter is set and the types are not compatible."); + } + + } + + /** + * Checks if the given converter can handle conversion between the given + * presentation and model type + * + * @param converter + * The converter to check + * @param presentationType + * The presentation type + * @param modelType + * The model type + * @return true if the converter supports conversion between the given + * presentation and model type, false otherwise + */ + public static boolean canConverterHandle(Converter<?, ?> converter, + Class<?> presentationType, Class<?> modelType) { + if (converter == null) { + return false; + } + + if (!modelType.isAssignableFrom(converter.getModelType())) { + return false; + } + if (!presentationType.isAssignableFrom(converter.getPresentationType())) { + return false; + } + + return true; + } +} diff --git a/src/com/vaadin/data/util/sqlcontainer/SQLContainer.java b/src/com/vaadin/data/util/sqlcontainer/SQLContainer.java index 3bf33defd5..e9b5b4fea3 100644 --- a/src/com/vaadin/data/util/sqlcontainer/SQLContainer.java +++ b/src/com/vaadin/data/util/sqlcontainer/SQLContainer.java @@ -36,9 +36,6 @@ import com.vaadin.data.util.sqlcontainer.query.generator.OracleGenerator; public class SQLContainer implements Container, Container.Filterable, Container.Indexed, Container.Sortable, Container.ItemSetChangeNotifier { - private static final Logger logger = Logger.getLogger(SQLContainer.class - .getName()); - /** Query delegate */ private QueryDelegate delegate; /** Auto commit mode, default = false */ @@ -132,6 +129,7 @@ public class SQLContainer implements Container, Container.Filterable, * * {@inheritDoc} */ + public Object addItem() throws UnsupportedOperationException { Object emptyKey[] = new Object[delegate.getPrimaryKeyColumns().size()]; RowId itemId = new TemporaryRowId(emptyKey); @@ -162,15 +160,15 @@ public class SQLContainer implements Container, Container.Filterable, if (notificationsEnabled) { CacheFlushNotifier.notifyOfCacheFlush(this); } - logger.log(Level.FINER, "Row added to DB..."); + getLogger().log(Level.FINER, "Row added to DB..."); return itemId; } catch (SQLException e) { - logger.log(Level.WARNING, + getLogger().log(Level.WARNING, "Failed to add row to DB. Rolling back.", e); try { delegate.rollback(); } catch (SQLException ee) { - logger.log(Level.SEVERE, + getLogger().log(Level.SEVERE, "Failed to roll back row addition", e); } return null; @@ -187,6 +185,7 @@ public class SQLContainer implements Container, Container.Filterable, * * @see com.vaadin.data.Container#containsId(java.lang.Object) */ + public boolean containsId(Object itemId) { if (itemId == null) { return false; @@ -215,7 +214,7 @@ public class SQLContainer implements Container, Container.Filterable, return delegate.containsRowWithKey(((RowId) itemId).getId()); } catch (Exception e) { /* Query failed, just return false. */ - logger.log(Level.WARNING, "containsId query failed", e); + getLogger().log(Level.WARNING, "containsId query failed", e); } } return false; @@ -227,6 +226,7 @@ public class SQLContainer implements Container, Container.Filterable, * @see com.vaadin.data.Container#getContainerProperty(java.lang.Object, * java.lang.Object) */ + public Property<?> getContainerProperty(Object itemId, Object propertyId) { Item item = getItem(itemId); if (item == null) { @@ -240,6 +240,7 @@ public class SQLContainer implements Container, Container.Filterable, * * @see com.vaadin.data.Container#getContainerPropertyIds() */ + public Collection<?> getContainerPropertyIds() { return Collections.unmodifiableCollection(propertyIds); } @@ -249,6 +250,7 @@ public class SQLContainer implements Container, Container.Filterable, * * @see com.vaadin.data.Container#getItem(java.lang.Object) */ + public Item getItem(Object itemId) { if (!cachedItems.containsKey(itemId)) { int index = indexOfId(itemId); @@ -295,6 +297,7 @@ public class SQLContainer implements Container, Container.Filterable, * * {@inheritDoc} */ + public Collection<?> getItemIds() { updateCount(); ArrayList<RowId> ids = new ArrayList<RowId>(); @@ -325,17 +328,18 @@ public class SQLContainer implements Container, Container.Filterable, rs.close(); delegate.commit(); } catch (SQLException e) { - logger.log(Level.WARNING, "getItemIds() failed, rolling back.", e); + getLogger().log(Level.WARNING, + "getItemIds() failed, rolling back.", e); try { delegate.rollback(); } catch (SQLException e1) { - logger.log(Level.SEVERE, "Failed to roll back state", e1); + getLogger().log(Level.SEVERE, "Failed to roll back state", e1); } try { rs.getStatement().close(); rs.close(); } catch (SQLException e1) { - logger.log(Level.WARNING, "Closing session failed", e1); + getLogger().log(Level.WARNING, "Closing session failed", e1); } throw new RuntimeException("Failed to fetch item indexes.", e); } @@ -350,6 +354,7 @@ public class SQLContainer implements Container, Container.Filterable, * * @see com.vaadin.data.Container#getType(java.lang.Object) */ + public Class<?> getType(Object propertyId) { if (!propertyIds.contains(propertyId)) { return null; @@ -362,6 +367,7 @@ public class SQLContainer implements Container, Container.Filterable, * * @see com.vaadin.data.Container#size() */ + public int size() { updateCount(); return size + sizeOfAddedItems() - removedItems.size(); @@ -372,6 +378,7 @@ public class SQLContainer implements Container, Container.Filterable, * * @see com.vaadin.data.Container#removeItem(java.lang.Object) */ + public boolean removeItem(Object itemId) throws UnsupportedOperationException { if (!containsId(itemId)) { @@ -400,29 +407,29 @@ public class SQLContainer implements Container, Container.Filterable, CacheFlushNotifier.notifyOfCacheFlush(this); } if (success) { - logger.log(Level.FINER, "Row removed from DB..."); + getLogger().log(Level.FINER, "Row removed from DB..."); } return success; } catch (SQLException e) { - logger.log(Level.WARNING, "Failed to remove row, rolling back", - e); + getLogger().log(Level.WARNING, + "Failed to remove row, rolling back", e); try { delegate.rollback(); } catch (SQLException ee) { /* Nothing can be done here */ - logger.log(Level.SEVERE, "Failed to rollback row removal", - ee); + getLogger().log(Level.SEVERE, + "Failed to rollback row removal", ee); } return false; } catch (OptimisticLockException e) { - logger.log(Level.WARNING, "Failed to remove row, rolling back", - e); + getLogger().log(Level.WARNING, + "Failed to remove row, rolling back", e); try { delegate.rollback(); } catch (SQLException ee) { /* Nothing can be done here */ - logger.log(Level.SEVERE, "Failed to rollback row removal", - ee); + getLogger().log(Level.SEVERE, + "Failed to rollback row removal", ee); } throw e; } @@ -439,6 +446,7 @@ public class SQLContainer implements Container, Container.Filterable, * * @see com.vaadin.data.Container#removeAllItems() */ + public boolean removeAllItems() throws UnsupportedOperationException { if (autoCommit) { /* Remove and commit instantly. */ @@ -452,7 +460,7 @@ public class SQLContainer implements Container, Container.Filterable, } if (success) { delegate.commit(); - logger.log(Level.FINER, "All rows removed from DB..."); + getLogger().log(Level.FINER, "All rows removed from DB..."); refresh(); if (notificationsEnabled) { CacheFlushNotifier.notifyOfCacheFlush(this); @@ -462,23 +470,23 @@ public class SQLContainer implements Container, Container.Filterable, } return success; } catch (SQLException e) { - logger.log(Level.WARNING, + getLogger().log(Level.WARNING, "removeAllItems() failed, rolling back", e); try { delegate.rollback(); } catch (SQLException ee) { /* Nothing can be done here */ - logger.log(Level.SEVERE, "Failed to roll back", ee); + getLogger().log(Level.SEVERE, "Failed to roll back", ee); } return false; } catch (OptimisticLockException e) { - logger.log(Level.WARNING, + getLogger().log(Level.WARNING, "removeAllItems() failed, rolling back", e); try { delegate.rollback(); } catch (SQLException ee) { /* Nothing can be done here */ - logger.log(Level.SEVERE, "Failed to roll back", ee); + getLogger().log(Level.SEVERE, "Failed to roll back", ee); } throw e; } @@ -499,6 +507,7 @@ public class SQLContainer implements Container, Container.Filterable, /** * {@inheritDoc} */ + public void addContainerFilter(Filter filter) throws UnsupportedFilterException { // filter.setCaseSensitive(!ignoreCase); @@ -510,6 +519,7 @@ public class SQLContainer implements Container, Container.Filterable, /** * {@inheritDoc} */ + public void removeContainerFilter(Filter filter) { filters.remove(filter); } @@ -549,6 +559,7 @@ public class SQLContainer implements Container, Container.Filterable, /** * {@inheritDoc} */ + public void removeAllContainerFilters() { filters.clear(); refresh(); @@ -563,6 +574,7 @@ public class SQLContainer implements Container, Container.Filterable, * * @see com.vaadin.data.Container.Indexed#indexOfId(java.lang.Object) */ + public int indexOfId(Object itemId) { // First check if the id is in the added items for (int ix = 0; ix < addedItems.size(); ix++) { @@ -609,6 +621,7 @@ public class SQLContainer implements Container, Container.Filterable, * * @see com.vaadin.data.Container.Indexed#getIdByIndex(int) */ + public Object getIdByIndex(int index) { if (index < 0 || index > size() - 1) { return null; @@ -635,6 +648,7 @@ public class SQLContainer implements Container, Container.Filterable, * * @see com.vaadin.data.Container.Ordered#nextItemId(java.lang.Object) */ + public Object nextItemId(Object itemId) { return getIdByIndex(indexOfId(itemId) + 1); } @@ -644,6 +658,7 @@ public class SQLContainer implements Container, Container.Filterable, * * @see com.vaadin.data.Container.Ordered#prevItemId(java.lang.Object) */ + public Object prevItemId(Object itemId) { return getIdByIndex(indexOfId(itemId) - 1); } @@ -653,6 +668,7 @@ public class SQLContainer implements Container, Container.Filterable, * * @see com.vaadin.data.Container.Ordered#firstItemId() */ + public Object firstItemId() { updateCount(); if (size == 0) { @@ -680,6 +696,7 @@ public class SQLContainer implements Container, Container.Filterable, * * @see com.vaadin.data.Container.Ordered#lastItemId() */ + public Object lastItemId() { if (addedItems.isEmpty()) { int lastIx = size() - 1; @@ -705,6 +722,7 @@ public class SQLContainer implements Container, Container.Filterable, * * @see com.vaadin.data.Container.Ordered#isFirstId(java.lang.Object) */ + public boolean isFirstId(Object itemId) { return firstItemId().equals(itemId); } @@ -714,6 +732,7 @@ public class SQLContainer implements Container, Container.Filterable, * * @see com.vaadin.data.Container.Ordered#isLastId(java.lang.Object) */ + public boolean isLastId(Object itemId) { return lastItemId().equals(itemId); } @@ -728,6 +747,7 @@ public class SQLContainer implements Container, Container.Filterable, * @see com.vaadin.data.Container.Sortable#sort(java.lang.Object[], * boolean[]) */ + public void sort(Object[] propertyId, boolean[] ascending) { sorters.clear(); if (propertyId == null || propertyId.length == 0) { @@ -743,7 +763,7 @@ public class SQLContainer implements Container, Container.Filterable, try { asc = ascending[i]; } catch (Exception e) { - logger.log(Level.WARNING, "", e); + getLogger().log(Level.WARNING, "", e); } sorters.add(new OrderBy((String) propertyId[i], asc)); } @@ -756,6 +776,7 @@ public class SQLContainer implements Container, Container.Filterable, * * @see com.vaadin.data.Container.Sortable#getSortableContainerPropertyIds() */ + public Collection<?> getSortableContainerPropertyIds() { return getContainerPropertyIds(); } @@ -872,7 +893,8 @@ public class SQLContainer implements Container, Container.Filterable, */ public void commit() throws UnsupportedOperationException, SQLException { try { - logger.log(Level.FINER, "Commiting changes through delegate..."); + getLogger().log(Level.FINER, + "Commiting changes through delegate..."); delegate.beginTransaction(); /* Perform buffered deletions */ for (RowItem item : removedItems.values()) { @@ -926,7 +948,7 @@ public class SQLContainer implements Container, Container.Filterable, * @throws SQLException */ public void rollback() throws UnsupportedOperationException, SQLException { - logger.log(Level.FINE, "Rolling back changes..."); + getLogger().log(Level.FINE, "Rolling back changes..."); removedItems.clear(); addedItems.clear(); modifiedItems.clear(); @@ -956,15 +978,15 @@ public class SQLContainer implements Container, Container.Filterable, if (notificationsEnabled) { CacheFlushNotifier.notifyOfCacheFlush(this); } - logger.log(Level.FINER, "Row updated to DB..."); + getLogger().log(Level.FINER, "Row updated to DB..."); } catch (SQLException e) { - logger.log(Level.WARNING, + getLogger().log(Level.WARNING, "itemChangeNotification failed, rolling back...", e); try { delegate.rollback(); } catch (SQLException ee) { /* Nothing can be done here */ - logger.log(Level.SEVERE, "Rollback failed", e); + getLogger().log(Level.SEVERE, "Rollback failed", e); } throw new RuntimeException(e); } @@ -1009,13 +1031,13 @@ public class SQLContainer implements Container, Container.Filterable, try { delegate.setFilters(filters); } catch (UnsupportedOperationException e) { - logger.log(Level.FINE, + getLogger().log(Level.FINE, "The query delegate doesn't support filtering", e); } try { delegate.setOrderBy(sorters); } catch (UnsupportedOperationException e) { - logger.log(Level.FINE, + getLogger().log(Level.FINE, "The query delegate doesn't support filtering", e); } int newSize = delegate.getCount(); @@ -1025,7 +1047,8 @@ public class SQLContainer implements Container, Container.Filterable, } sizeUpdated = new Date(); sizeDirty = false; - logger.log(Level.FINER, "Updated row count. New count is: " + size); + getLogger().log(Level.FINER, + "Updated row count. New count is: " + size); } catch (SQLException e) { throw new RuntimeException("Failed to update item set size.", e); } @@ -1069,7 +1092,7 @@ public class SQLContainer implements Container, Container.Filterable, try { type = Class.forName(rsmd.getColumnClassName(i)); } catch (Exception e) { - logger.log(Level.WARNING, "Class not found", e); + getLogger().log(Level.WARNING, "Class not found", e); /* On failure revert to Object and hope for the best. */ type = Object.class; } @@ -1095,14 +1118,14 @@ public class SQLContainer implements Container, Container.Filterable, rs.getStatement().close(); rs.close(); delegate.commit(); - logger.log(Level.FINER, "Property IDs fetched."); + getLogger().log(Level.FINER, "Property IDs fetched."); } catch (SQLException e) { - logger.log(Level.WARNING, + getLogger().log(Level.WARNING, "Failed to fetch property ids, rolling back", e); try { delegate.rollback(); } catch (SQLException e1) { - logger.log(Level.SEVERE, "Failed to roll back", e1); + getLogger().log(Level.SEVERE, "Failed to roll back", e1); } try { if (rs != null) { @@ -1112,7 +1135,7 @@ public class SQLContainer implements Container, Container.Filterable, rs.close(); } } catch (SQLException e1) { - logger.log(Level.WARNING, "Failed to close session", e1); + getLogger().log(Level.WARNING, "Failed to close session", e1); } throw e; } @@ -1135,7 +1158,7 @@ public class SQLContainer implements Container, Container.Filterable, } catch (UnsupportedOperationException e) { /* The query delegate doesn't support sorting. */ /* No need to do anything. */ - logger.log(Level.FINE, + getLogger().log(Level.FINE, "The query delegate doesn't support sorting", e); } delegate.beginTransaction(); @@ -1217,14 +1240,17 @@ public class SQLContainer implements Container, Container.Filterable, rs.getStatement().close(); rs.close(); delegate.commit(); - logger.log(Level.FINER, "Fetched " + pageLength * CACHE_RATIO - + " rows starting from " + currentOffset); + getLogger().log( + Level.FINER, + "Fetched " + pageLength * CACHE_RATIO + + " rows starting from " + currentOffset); } catch (SQLException e) { - logger.log(Level.WARNING, "Failed to fetch rows, rolling back", e); + getLogger().log(Level.WARNING, + "Failed to fetch rows, rolling back", e); try { delegate.rollback(); } catch (SQLException e1) { - logger.log(Level.SEVERE, "Failed to roll back", e1); + getLogger().log(Level.SEVERE, "Failed to roll back", e1); } try { if (rs != null) { @@ -1234,7 +1260,7 @@ public class SQLContainer implements Container, Container.Filterable, } } } catch (SQLException e1) { - logger.log(Level.WARNING, "Failed to close session", e1); + getLogger().log(Level.WARNING, "Failed to close session", e1); } throw new RuntimeException("Failed to fetch page.", e); } @@ -1324,6 +1350,7 @@ public class SQLContainer implements Container, Container.Filterable, * @see com.vaadin.data.Container#addContainerProperty(java.lang.Object, * java.lang.Class, java.lang.Object) */ + public boolean addContainerProperty(Object propertyId, Class<?> type, Object defaultValue) throws UnsupportedOperationException { throw new UnsupportedOperationException(); @@ -1334,6 +1361,7 @@ public class SQLContainer implements Container, Container.Filterable, * * @see com.vaadin.data.Container#removeContainerProperty(java.lang.Object) */ + public boolean removeContainerProperty(Object propertyId) throws UnsupportedOperationException { throw new UnsupportedOperationException(); @@ -1344,6 +1372,7 @@ public class SQLContainer implements Container, Container.Filterable, * * @see com.vaadin.data.Container#addItem(java.lang.Object) */ + public Item addItem(Object itemId) throws UnsupportedOperationException { throw new UnsupportedOperationException(); } @@ -1354,6 +1383,7 @@ public class SQLContainer implements Container, Container.Filterable, * @see com.vaadin.data.Container.Ordered#addItemAfter(java.lang.Object, * java.lang.Object) */ + public Item addItemAfter(Object previousItemId, Object newItemId) throws UnsupportedOperationException { throw new UnsupportedOperationException(); @@ -1364,6 +1394,7 @@ public class SQLContainer implements Container, Container.Filterable, * * @see com.vaadin.data.Container.Indexed#addItemAt(int, java.lang.Object) */ + public Item addItemAt(int index, Object newItemId) throws UnsupportedOperationException { throw new UnsupportedOperationException(); @@ -1374,6 +1405,7 @@ public class SQLContainer implements Container, Container.Filterable, * * @see com.vaadin.data.Container.Indexed#addItemAt(int) */ + public Object addItemAt(int index) throws UnsupportedOperationException { throw new UnsupportedOperationException(); } @@ -1383,6 +1415,7 @@ public class SQLContainer implements Container, Container.Filterable, * * @see com.vaadin.data.Container.Ordered#addItemAfter(java.lang.Object) */ + public Object addItemAfter(Object previousItemId) throws UnsupportedOperationException { throw new UnsupportedOperationException(); @@ -1399,6 +1432,7 @@ public class SQLContainer implements Container, Container.Filterable, * com.vaadin.data.Container.ItemSetChangeNotifier#addListener(com.vaadin * .data.Container.ItemSetChangeListener) */ + public void addListener(Container.ItemSetChangeListener listener) { if (itemSetChangeListeners == null) { itemSetChangeListeners = new LinkedList<Container.ItemSetChangeListener>(); @@ -1413,6 +1447,7 @@ public class SQLContainer implements Container, Container.Filterable, * com.vaadin.data.Container.ItemSetChangeNotifier#removeListener(com.vaadin * .data.Container.ItemSetChangeListener) */ + public void removeListener(Container.ItemSetChangeListener listener) { if (itemSetChangeListeners != null) { itemSetChangeListeners.remove(listener); @@ -1577,7 +1612,8 @@ public class SQLContainer implements Container, Container.Filterable, r.getReferencedColumn())); return true; } catch (Exception e) { - logger.log(Level.WARNING, "Setting referenced item failed.", e); + getLogger() + .log(Level.WARNING, "Setting referenced item failed.", e); return false; } } @@ -1640,4 +1676,7 @@ public class SQLContainer implements Container, Container.Filterable, } } + private static final Logger getLogger() { + return Logger.getLogger(SQLContainer.class.getName()); + } } diff --git a/src/com/vaadin/data/util/sqlcontainer/connection/J2EEConnectionPool.java b/src/com/vaadin/data/util/sqlcontainer/connection/J2EEConnectionPool.java index 9e4bb772f5..40d0d0426f 100644 --- a/src/com/vaadin/data/util/sqlcontainer/connection/J2EEConnectionPool.java +++ b/src/com/vaadin/data/util/sqlcontainer/connection/J2EEConnectionPool.java @@ -13,8 +13,6 @@ import javax.naming.NamingException; import javax.sql.DataSource; public class J2EEConnectionPool implements JDBCConnectionPool { - private static final Logger logger = Logger - .getLogger(J2EEConnectionPool.class.getName()); private String dataSourceJndiName; @@ -58,7 +56,8 @@ public class J2EEConnectionPool implements JDBCConnectionPool { try { conn.close(); } catch (SQLException e) { - logger.log(Level.FINE, "Could not release SQL connection", e); + Logger.getLogger(J2EEConnectionPool.class.getName()).log( + Level.FINE, "Could not release SQL connection", e); } } } diff --git a/src/com/vaadin/data/util/sqlcontainer/query/TableQuery.java b/src/com/vaadin/data/util/sqlcontainer/query/TableQuery.java index 7e546309f6..22ca30cc32 100644 --- a/src/com/vaadin/data/util/sqlcontainer/query/TableQuery.java +++ b/src/com/vaadin/data/util/sqlcontainer/query/TableQuery.java @@ -38,9 +38,6 @@ import com.vaadin.data.util.sqlcontainer.query.generator.StatementHelper; public class TableQuery implements QueryDelegate, QueryDelegate.RowIdChangeNotifier { - private static final Logger logger = Logger.getLogger(TableQuery.class - .getName()); - /** Table name, primary key column name(s) and version column name */ private String tableName; private List<String> primaryKeyColumns; @@ -115,7 +112,7 @@ public class TableQuery implements QueryDelegate, * @see com.vaadin.addon.sqlcontainer.query.QueryDelegate#getCount() */ public int getCount() throws SQLException { - logger.log(Level.FINE, "Fetching count..."); + getLogger().log(Level.FINE, "Fetching count..."); StatementHelper sh = sqlGenerator.generateSelectQuery(tableName, filters, null, 0, 0, "COUNT(*)"); boolean shouldCloseTransaction = false; @@ -228,7 +225,7 @@ public class TableQuery implements QueryDelegate, PreparedStatement pstmt = activeConnection.prepareStatement( sh.getQueryString(), primaryKeyColumns.toArray(new String[0])); sh.setParameterValuesToStatement(pstmt); - logger.log(Level.FINE, "DB -> " + sh.getQueryString()); + getLogger().log(Level.FINE, "DB -> " + sh.getQueryString()); int result = pstmt.executeUpdate(); if (result > 0) { /* @@ -293,7 +290,7 @@ public class TableQuery implements QueryDelegate, throw new IllegalStateException(); } - logger.log(Level.FINE, "DB -> begin transaction"); + getLogger().log(Level.FINE, "DB -> begin transaction"); activeConnection = connectionPool.reserveConnection(); activeConnection.setAutoCommit(false); transactionOpen = true; @@ -306,7 +303,7 @@ public class TableQuery implements QueryDelegate, */ public void commit() throws UnsupportedOperationException, SQLException { if (transactionOpen && activeConnection != null) { - logger.log(Level.FINE, "DB -> commit"); + getLogger().log(Level.FINE, "DB -> commit"); activeConnection.commit(); connectionPool.releaseConnection(activeConnection); } else { @@ -334,7 +331,7 @@ public class TableQuery implements QueryDelegate, */ public void rollback() throws UnsupportedOperationException, SQLException { if (transactionOpen && activeConnection != null) { - logger.log(Level.FINE, "DB -> rollback"); + getLogger().log(Level.FINE, "DB -> rollback"); activeConnection.rollback(); connectionPool.releaseConnection(activeConnection); } else { @@ -389,7 +386,7 @@ public class TableQuery implements QueryDelegate, } PreparedStatement pstmt = c.prepareStatement(sh.getQueryString()); sh.setParameterValuesToStatement(pstmt); - logger.log(Level.FINE, "DB -> " + sh.getQueryString()); + getLogger().log(Level.FINE, "DB -> " + sh.getQueryString()); return pstmt.executeQuery(); } @@ -415,7 +412,7 @@ public class TableQuery implements QueryDelegate, } pstmt = c.prepareStatement(sh.getQueryString()); sh.setParameterValuesToStatement(pstmt); - logger.log(Level.FINE, "DB -> " + sh.getQueryString()); + getLogger().log(Level.FINE, "DB -> " + sh.getQueryString()); int retval = pstmt.executeUpdate(); return retval; } finally { @@ -458,7 +455,7 @@ public class TableQuery implements QueryDelegate, pstmt = c.prepareStatement(sh.getQueryString(), primaryKeyColumns.toArray(new String[0])); sh.setParameterValuesToStatement(pstmt); - logger.log(Level.FINE, "DB -> " + sh.getQueryString()); + getLogger().log(Level.FINE, "DB -> " + sh.getQueryString()); int result = pstmt.executeUpdate(); genKeys = pstmt.getGeneratedKeys(); RowId newId = getNewRowId(row, genKeys); @@ -571,7 +568,7 @@ public class TableQuery implements QueryDelegate, } return new RowId(newRowId.toArray()); } catch (Exception e) { - logger.log(Level.FINE, + getLogger().log(Level.FINE, "Failed to fetch key values on insert: " + e.getMessage()); return null; } @@ -586,8 +583,8 @@ public class TableQuery implements QueryDelegate, */ public boolean removeRow(RowItem row) throws UnsupportedOperationException, SQLException { - logger.log(Level.FINE, "Removing row with id: " - + row.getId().getId()[0].toString()); + getLogger().log(Level.FINE, + "Removing row with id: " + row.getId().getId()[0].toString()); if (executeUpdate(sqlGenerator.generateDeleteQuery(getTableName(), primaryKeyColumns, versionColumn, row)) == 1) { return true; @@ -695,4 +692,8 @@ public class TableQuery implements QueryDelegate, rowIdChangeListeners.remove(listener); } } + + private static final Logger getLogger() { + return Logger.getLogger(TableQuery.class.getName()); + } } diff --git a/src/com/vaadin/event/ListenerMethod.java b/src/com/vaadin/event/ListenerMethod.java index 1f1305fa69..f7dc8a7f13 100644 --- a/src/com/vaadin/event/ListenerMethod.java +++ b/src/com/vaadin/event/ListenerMethod.java @@ -43,9 +43,6 @@ import java.util.logging.Logger; @SuppressWarnings("serial") public class ListenerMethod implements EventListener, Serializable { - private static final Logger logger = Logger.getLogger(ListenerMethod.class - .getName()); - /** * Type of the event that should trigger this listener. Also the subclasses * of this class are accepted to trigger the listener. @@ -84,9 +81,10 @@ public class ListenerMethod implements EventListener, Serializable { out.writeObject(name); out.writeObject(paramTypes); } catch (NotSerializableException e) { - logger.warning("Error in serialization of the application: Class " - + target.getClass().getName() - + " must implement serialization."); + getLogger().warning( + "Error in serialization of the application: Class " + + target.getClass().getName() + + " must implement serialization."); throw e; } @@ -103,7 +101,7 @@ public class ListenerMethod implements EventListener, Serializable { // inner classes method = findHighestMethod(target.getClass(), name, paramTypes); } catch (SecurityException e) { - logger.log(Level.SEVERE, "Internal deserialization error", e); + getLogger().log(Level.SEVERE, "Internal deserialization error", e); } }; @@ -658,4 +656,8 @@ public class ListenerMethod implements EventListener, Serializable { return target; } + private static final Logger getLogger() { + return Logger.getLogger(ListenerMethod.class.getName()); + } + } diff --git a/src/com/vaadin/event/dd/acceptcriteria/AcceptAll.java b/src/com/vaadin/event/dd/acceptcriteria/AcceptAll.java index 87193e8b3e..52ffab9263 100644 --- a/src/com/vaadin/event/dd/acceptcriteria/AcceptAll.java +++ b/src/com/vaadin/event/dd/acceptcriteria/AcceptAll.java @@ -7,7 +7,6 @@ package com.vaadin.event.dd.acceptcriteria; import com.vaadin.event.dd.DragAndDropEvent; -import com.vaadin.terminal.gwt.client.ui.dd.VAcceptAll; /** * Criterion that accepts all drops anywhere on the component. @@ -18,7 +17,6 @@ import com.vaadin.terminal.gwt.client.ui.dd.VAcceptAll; * @since 6.3 * */ -@ClientCriterion(VAcceptAll.class) public final class AcceptAll extends ClientSideCriterion { private static final long serialVersionUID = 7406683402153141461L; diff --git a/src/com/vaadin/event/dd/acceptcriteria/And.java b/src/com/vaadin/event/dd/acceptcriteria/And.java index 89c8cb377b..a0340a9cdb 100644 --- a/src/com/vaadin/event/dd/acceptcriteria/And.java +++ b/src/com/vaadin/event/dd/acceptcriteria/And.java @@ -19,7 +19,6 @@ import com.vaadin.terminal.PaintTarget; * @since 6.3 * */ -@ClientCriterion(com.vaadin.terminal.gwt.client.ui.dd.VAnd.class) public class And extends ClientSideCriterion { private static final long serialVersionUID = -5242574480825471748L; diff --git a/src/com/vaadin/event/dd/acceptcriteria/ClientCriterion.java b/src/com/vaadin/event/dd/acceptcriteria/ClientCriterion.java deleted file mode 100644 index 8b64106be7..0000000000 --- a/src/com/vaadin/event/dd/acceptcriteria/ClientCriterion.java +++ /dev/null @@ -1,29 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.event.dd.acceptcriteria; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import com.vaadin.terminal.gwt.client.ui.dd.VAcceptCriterion; - -/** - * An annotation type used to point the client side counterpart for server side - * a {@link AcceptCriterion} class. - * <p> - * Annotations are used at GWT compilation phase, so remember to rebuild your - * widgetset if you do changes for {@link ClientCriterion} mappings. - * - * @since 6.3 - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -public @interface ClientCriterion { - /** - * @return the client side counterpart for the annotated criterion - */ - Class<? extends VAcceptCriterion> value(); -} diff --git a/src/com/vaadin/event/dd/acceptcriteria/ContainsDataFlavor.java b/src/com/vaadin/event/dd/acceptcriteria/ContainsDataFlavor.java index 298a50a1d5..7eb04d9647 100644 --- a/src/com/vaadin/event/dd/acceptcriteria/ContainsDataFlavor.java +++ b/src/com/vaadin/event/dd/acceptcriteria/ContainsDataFlavor.java @@ -10,7 +10,6 @@ import com.vaadin.event.Transferable; import com.vaadin.event.dd.DragAndDropEvent; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; -import com.vaadin.terminal.gwt.client.ui.dd.VContainsDataFlavor; /** * A Criterion that checks whether {@link Transferable} contains given data @@ -19,7 +18,6 @@ import com.vaadin.terminal.gwt.client.ui.dd.VContainsDataFlavor; * * @since 6.3 */ -@ClientCriterion(VContainsDataFlavor.class) public class ContainsDataFlavor extends ClientSideCriterion { private String dataFlavorId; diff --git a/src/com/vaadin/event/dd/acceptcriteria/Not.java b/src/com/vaadin/event/dd/acceptcriteria/Not.java index 774dcc642e..85ff7e9bc9 100644 --- a/src/com/vaadin/event/dd/acceptcriteria/Not.java +++ b/src/com/vaadin/event/dd/acceptcriteria/Not.java @@ -9,7 +9,6 @@ package com.vaadin.event.dd.acceptcriteria; import com.vaadin.event.dd.DragAndDropEvent; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; -import com.vaadin.terminal.gwt.client.ui.dd.VNot; /** * Criterion that wraps another criterion and inverts its return value. @@ -17,7 +16,6 @@ import com.vaadin.terminal.gwt.client.ui.dd.VNot; * @since 6.3 * */ -@ClientCriterion(VNot.class) public class Not extends ClientSideCriterion { private static final long serialVersionUID = 1131422338558613244L; diff --git a/src/com/vaadin/event/dd/acceptcriteria/Or.java b/src/com/vaadin/event/dd/acceptcriteria/Or.java index 2e7cdf45a3..077e9114d8 100644 --- a/src/com/vaadin/event/dd/acceptcriteria/Or.java +++ b/src/com/vaadin/event/dd/acceptcriteria/Or.java @@ -9,7 +9,6 @@ package com.vaadin.event.dd.acceptcriteria; import com.vaadin.event.dd.DragAndDropEvent; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; -import com.vaadin.terminal.gwt.client.ui.dd.VOr; /** * A compound criterion that accepts the drag if any of its criterion accepts @@ -20,7 +19,6 @@ import com.vaadin.terminal.gwt.client.ui.dd.VOr; * @since 6.3 * */ -@ClientCriterion(VOr.class) public class Or extends ClientSideCriterion { private static final long serialVersionUID = 1L; private AcceptCriterion criteria[]; diff --git a/src/com/vaadin/event/dd/acceptcriteria/ServerSideCriterion.java b/src/com/vaadin/event/dd/acceptcriteria/ServerSideCriterion.java index b3f4702c51..77840bc94f 100644 --- a/src/com/vaadin/event/dd/acceptcriteria/ServerSideCriterion.java +++ b/src/com/vaadin/event/dd/acceptcriteria/ServerSideCriterion.java @@ -8,7 +8,6 @@ import java.io.Serializable; import com.vaadin.event.Transferable; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; -import com.vaadin.terminal.gwt.client.ui.dd.VServerAccept; /** * Parent class for criteria which are verified on the server side during a drag @@ -27,7 +26,6 @@ import com.vaadin.terminal.gwt.client.ui.dd.VServerAccept; * * @since 6.3 */ -@ClientCriterion(VServerAccept.class) public abstract class ServerSideCriterion implements Serializable, AcceptCriterion { diff --git a/src/com/vaadin/event/dd/acceptcriteria/SourceIs.java b/src/com/vaadin/event/dd/acceptcriteria/SourceIs.java index 0d29e9a327..d7c47dc915 100644 --- a/src/com/vaadin/event/dd/acceptcriteria/SourceIs.java +++ b/src/com/vaadin/event/dd/acceptcriteria/SourceIs.java @@ -13,7 +13,6 @@ import com.vaadin.event.TransferableImpl; import com.vaadin.event.dd.DragAndDropEvent; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; -import com.vaadin.terminal.gwt.client.ui.dd.VDragSourceIs; import com.vaadin.ui.Component; /** @@ -23,10 +22,7 @@ import com.vaadin.ui.Component; * @since 6.3 */ @SuppressWarnings("serial") -@ClientCriterion(VDragSourceIs.class) public class SourceIs extends ClientSideCriterion { - private static final Logger logger = Logger.getLogger(SourceIs.class - .getName()); private Component[] components; @@ -43,11 +39,11 @@ public class SourceIs extends ClientSideCriterion { if (c.getApplication() != null) { target.addAttribute("component" + paintedComponents++, c); } else { - logger.log( - Level.WARNING, - "SourceIs component {0} at index {1} is not attached to the component hierachy and will thus be ignored", - new Object[] { c.getClass().getName(), - Integer.valueOf(i) }); + Logger.getLogger(SourceIs.class.getName()) + .log(Level.WARNING, + "SourceIs component {0} at index {1} is not attached to the component hierachy and will thus be ignored", + new Object[] { c.getClass().getName(), + Integer.valueOf(i) }); } } target.addAttribute("c", paintedComponents); diff --git a/src/com/vaadin/event/dd/acceptcriteria/SourceIsTarget.java b/src/com/vaadin/event/dd/acceptcriteria/SourceIsTarget.java index 8303809140..454159b3da 100644 --- a/src/com/vaadin/event/dd/acceptcriteria/SourceIsTarget.java +++ b/src/com/vaadin/event/dd/acceptcriteria/SourceIsTarget.java @@ -10,7 +10,6 @@ import com.vaadin.event.Transferable; import com.vaadin.event.TransferableImpl; import com.vaadin.event.dd.DragAndDropEvent; import com.vaadin.event.dd.DropTarget; -import com.vaadin.terminal.gwt.client.ui.dd.VSourceIsTarget; import com.vaadin.ui.Component; import com.vaadin.ui.Table; import com.vaadin.ui.Tree; @@ -26,7 +25,6 @@ import com.vaadin.ui.Tree; * @since 6.3 * */ -@ClientCriterion(VSourceIsTarget.class) public class SourceIsTarget extends ClientSideCriterion { private static final long serialVersionUID = -451399314705532584L; diff --git a/src/com/vaadin/event/dd/acceptcriteria/TargetDetailIs.java b/src/com/vaadin/event/dd/acceptcriteria/TargetDetailIs.java index 25371938d2..641fddc3ea 100644 --- a/src/com/vaadin/event/dd/acceptcriteria/TargetDetailIs.java +++ b/src/com/vaadin/event/dd/acceptcriteria/TargetDetailIs.java @@ -10,7 +10,6 @@ import com.vaadin.event.dd.DragAndDropEvent; import com.vaadin.event.dd.TargetDetails; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; -import com.vaadin.terminal.gwt.client.ui.dd.VTargetDetailIs; /** * Criterion for checking if drop target details contains the specific property @@ -21,7 +20,6 @@ import com.vaadin.terminal.gwt.client.ui.dd.VTargetDetailIs; * TODO add support for other basic data types that we support in UIDL. * */ -@ClientCriterion(VTargetDetailIs.class) public class TargetDetailIs extends ClientSideCriterion { private static final long serialVersionUID = 763165450054331246L; diff --git a/src/com/vaadin/navigator/FragmentManager.java b/src/com/vaadin/navigator/FragmentManager.java new file mode 100644 index 0000000000..f1fd90e569 --- /dev/null +++ b/src/com/vaadin/navigator/FragmentManager.java @@ -0,0 +1,38 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.navigator; + +import java.io.Serializable; + +/** + * Fragment manager that handles interaction between Navigator and URI fragments + * or other similar view identification and bookmarking system. + * + * Alternative implementations can be created for HTML5 pushState, for portlet + * URL navigation and other similar systems. + * + * This interface is mostly for internal use by {@link Navigator}. + * + * @author Vaadin Ltd + * @since 7.0 + */ +public interface FragmentManager extends Serializable { + /** + * Return the current fragment (location string) including view name and any + * optional parameters. + * + * @return current view and parameter string, not null + */ + public String getFragment(); + + /** + * Set the current fragment (location string) in the application URL or + * similar location, including view name and any optional parameters. + * + * @param fragment + * new view and parameter string, not null + */ + public void setFragment(String fragment); +}
\ No newline at end of file diff --git a/src/com/vaadin/navigator/Navigator.java b/src/com/vaadin/navigator/Navigator.java new file mode 100644 index 0000000000..3ff727b504 --- /dev/null +++ b/src/com/vaadin/navigator/Navigator.java @@ -0,0 +1,586 @@ +package com.vaadin.navigator; + +/* + @VaadinApache2LicenseForJavaFiles@ + */ + +import java.io.Serializable; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import com.vaadin.navigator.ViewChangeListener.ViewChangeEvent; +import com.vaadin.terminal.Page; +import com.vaadin.terminal.Page.FragmentChangedEvent; +import com.vaadin.terminal.Page.FragmentChangedListener; +import com.vaadin.ui.Component; +import com.vaadin.ui.CssLayout; +import com.vaadin.ui.CustomComponent; + +/** + * Navigator utility that allows switching of views in a part of an application. + * + * The view switching can be based e.g. on URI fragments containing the view + * name and parameters to the view. There are two types of parameters for views: + * an optional parameter string that is included in the fragment (may be + * bookmarkable). + * + * Views can be explicitly registered or dynamically generated and listening to + * view changes is possible. + * + * Note that {@link Navigator} is not a component itself but comes with + * {@link SimpleViewDisplay} which is a component that displays the selected + * view as its contents. + * + * @author Vaadin Ltd + * @since 7.0 + */ +public class Navigator implements Serializable { + + // TODO divert navigation e.g. if no permissions? Or just show another view + // but keep URL? how best to intercept + // TODO investigate relationship with TouchKit navigation support + + /** + * Empty view component. + */ + public static class EmptyView extends CssLayout implements View { + /** + * Create minimally sized empty view. + */ + public EmptyView() { + setWidth("0px"); + setHeight("0px"); + } + + public void navigateTo(String fragmentParameters) { + // nothing to do + } + } + + /** + * Fragment manager using URI fragments of a Page to track views and enable + * listening to view changes. + * + * This class is mostly for internal use by Navigator, and is only public + * and static to enable testing. + */ + public static class UriFragmentManager implements FragmentManager, + FragmentChangedListener { + private final Page page; + private final Navigator navigator; + + /** + * Create a new URIFragmentManager and attach it to listen to URI + * fragment changes of a {@link Page}. + * + * @param page + * page whose URI fragment to get and modify + * @param navigator + * {@link Navigator} to notify of fragment changes (using + * {@link Navigator#navigateTo(String)} + */ + public UriFragmentManager(Page page, Navigator navigator) { + this.page = page; + this.navigator = navigator; + + page.addListener(this); + } + + public String getFragment() { + return page.getFragment(); + } + + public void setFragment(String fragment) { + page.setFragment(fragment, false); + } + + public void fragmentChanged(FragmentChangedEvent event) { + UriFragmentManager.this.navigator.navigateTo(getFragment()); + } + } + + /** + * View display that is a component itself and replaces its contents with + * the view. + * + * This display only supports views that are {@link Component}s themselves. + * Attempting to display a view that is not a component causes an exception + * to be thrown. + * + * By default, the view display has full size. + */ + public static class SimpleViewDisplay extends CustomComponent implements + ViewDisplay { + + /** + * Create new {@link ViewDisplay} that is itself a component displaying + * the view. + */ + public SimpleViewDisplay() { + setSizeFull(); + } + + public void showView(View view) { + if (view instanceof Component) { + setCompositionRoot((Component) view); + } else { + throw new IllegalArgumentException("View is not a component: " + + view); + } + } + } + + /** + * View provider which supports mapping a single view name to a single + * pre-initialized view instance. + * + * For most cases, ClassBasedViewProvider should be used instead of this. + */ + public static class StaticViewProvider implements ViewProvider { + private final String viewName; + private final View view; + + /** + * Create a new view provider which returns a pre-created view instance. + * + * @param viewName + * name of the view (not null) + * @param view + * view instance to return (not null), reused on every + * request + */ + public StaticViewProvider(String viewName, View view) { + this.viewName = viewName; + this.view = view; + } + + public String getViewName(String viewAndParameters) { + if (null == viewAndParameters) { + return null; + } + if (viewAndParameters.startsWith(viewName)) { + return viewName; + } + return null; + } + + public View getView(String viewName) { + if (this.viewName.equals(viewName)) { + return view; + } + return null; + } + + /** + * Get the view name for this provider. + * + * @return view name for this provider + */ + public String getViewName() { + return viewName; + } + } + + /** + * View provider which maps a single view name to a class to instantiate for + * the view. + * + * Note that the view class must be accessible by the class loader used by + * the provider. This may require its visibility to be public. + * + * This class is primarily for internal use by {@link Navigator}. + */ + public static class ClassBasedViewProvider implements ViewProvider { + + private final String viewName; + private final Class<? extends View> viewClass; + + /** + * Create a new view provider which creates new view instances based on + * a view class. + * + * @param viewName + * name of the views to create (not null) + * @param viewClass + * class to instantiate when a view is requested (not null) + */ + public ClassBasedViewProvider(String viewName, + Class<? extends View> viewClass) { + if (null == viewName || null == viewClass) { + throw new IllegalArgumentException( + "View name and class should not be null"); + } + this.viewName = viewName; + this.viewClass = viewClass; + } + + public String getViewName(String viewAndParameters) { + if (null == viewAndParameters) { + return null; + } + if (viewAndParameters.equals(viewName) + || viewAndParameters.startsWith(viewName + "/")) { + return viewName; + } + return null; + } + + public View getView(String viewName) { + if (this.viewName.equals(viewName)) { + try { + View view = viewClass.newInstance(); + return view; + } catch (InstantiationException e) { + // TODO error handling + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + // TODO error handling + throw new RuntimeException(e); + } + } + return null; + } + + /** + * Get the view name for this provider. + * + * @return view name for this provider + */ + public String getViewName() { + return viewName; + } + + /** + * Get the view class for this provider. + * + * @return {@link View} class + */ + public Class<? extends View> getViewClass() { + return viewClass; + } + } + + private final FragmentManager fragmentManager; + private final ViewDisplay display; + private View currentView = null; + private List<ViewChangeListener> listeners = new LinkedList<ViewChangeListener>(); + private List<ViewProvider> providers = new LinkedList<ViewProvider>(); + + /** + * Create a navigator that is tracking the active view using URI fragments. + * + * @param page + * whose URI fragments are used + * @param display + * where to display the views + */ + public Navigator(Page page, ViewDisplay display) { + this.display = display; + fragmentManager = new UriFragmentManager(page, this); + navigateTo(page.getFragment()); + } + + /** + * Create a navigator that is tracking the active view using URI fragments. + * By default, a {@link SimpleViewDisplay} is used and can be obtained using + * {@link #getDisplay()}. + * + * @param page + * whose URI fragments are used + */ + public Navigator(Page page) { + display = new SimpleViewDisplay(); + fragmentManager = new UriFragmentManager(page, this); + navigateTo(page.getFragment()); + } + + /** + * Create a navigator. + * + * When a custom fragment manager is not needed, use the constructor + * {@link #Navigator(Page, ViewDisplay)} which uses a URI fragment based + * fragment manager. + * + * Note that navigation to the initial view must be performed explicitly by + * the application after creating a Navigator using this constructor. + * + * @param fragmentManager + * fragment manager keeping track of the active view and enabling + * bookmarking and direct navigation + * @param display + * where to display the views + */ + public Navigator(FragmentManager fragmentManager, ViewDisplay display) { + this.display = display; + this.fragmentManager = fragmentManager; + } + + /** + * Navigate to a view and initialize the view with given parameters. + * + * The view string consists of a view name optionally followed by a slash + * and (fragment) parameters. ViewProviders are used to find and create the + * correct type of view. + * + * If multiple providers return a matching view, the view with the longest + * name is selected. This way, e.g. hierarchies of subviews can be + * registered like "admin/", "admin/users", "admin/settings" and the longest + * match is used. + * + * If the view being deactivated indicates it wants a confirmation for the + * navigation operation, the user is asked for the confirmation. + * + * Registered {@link ViewChangeListener}s are called upon successful view + * change. + * + * @param viewAndParameters + * view name and parameters + */ + public void navigateTo(String viewAndParameters) { + String longestViewName = null; + View viewWithLongestName = null; + for (ViewProvider provider : providers) { + String viewName = provider.getViewName(viewAndParameters); + if (null != viewName + && (longestViewName == null || viewName.length() > longestViewName + .length())) { + View view = provider.getView(viewName); + if (null != view) { + longestViewName = viewName; + viewWithLongestName = view; + } + } + } + if (viewWithLongestName != null) { + String parameters = null; + if (viewAndParameters.length() > longestViewName.length() + 1) { + parameters = viewAndParameters.substring(longestViewName + .length() + 1); + } + navigateTo(viewWithLongestName, longestViewName, parameters); + } + // TODO if no view is found, what to do? + } + + /** + * Internal method activating a view, setting its parameters and calling + * listeners. + * + * This method also verifies that the user is allowed to perform the + * navigation operation. + * + * @param view + * view to activate + * @param viewName + * (optional) name of the view or null not to set the fragment + * @param fragmentParameters + * parameters passed in the fragment for the view + */ + protected void navigateTo(View view, String viewName, + String fragmentParameters) { + ViewChangeEvent event = new ViewChangeEvent(this, currentView, view, + viewName, fragmentParameters); + if (!isViewChangeAllowed(event)) { + return; + } + + if (null != viewName && getFragmentManager() != null) { + String currentFragment = viewName; + if (fragmentParameters != null) { + currentFragment += "/" + fragmentParameters; + } + if (!currentFragment.equals(getFragmentManager().getFragment())) { + getFragmentManager().setFragment(currentFragment); + } + } + + view.navigateTo(fragmentParameters); + currentView = view; + + if (display != null) { + display.showView(view); + } + + fireViewChange(event); + } + + /** + * Check whether view change is allowed. + * + * All related listeners are called. The view change is blocked if any of + * them wants to block the navigation operation. + * + * The view change listeners may also e.g. open a warning or question dialog + * and save the parameters to re-initiate the navigation operation upon user + * action. + * + * @param event + * view change event (not null, view change not yet performed) + * @return true if the view change should be allowed, false to silently + * block the navigation operation + */ + protected boolean isViewChangeAllowed(ViewChangeEvent event) { + for (ViewChangeListener l : listeners) { + if (!l.isViewChangeAllowed(event)) { + return false; + } + } + return true; + } + + /** + * Return the fragment manager that is used to get, listen to and manipulate + * the URI fragment or other source of navigation information. + * + * @return fragment manager in use + */ + protected FragmentManager getFragmentManager() { + return fragmentManager; + } + + /** + * Returns the ViewDisplay used by the navigator. Unless another display is + * specified, a {@link SimpleViewDisplay} (which is a {@link Component}) is + * used by default. + * + * @return current ViewDisplay + */ + public ViewDisplay getDisplay() { + return display; + } + + /** + * Fire an event when the current view has changed. + * + * @param event + * view change event (not null) + */ + protected void fireViewChange(ViewChangeEvent event) { + for (ViewChangeListener l : listeners) { + l.navigatorViewChanged(event); + } + } + + /** + * Register a static, pre-initialized view instance for a view name. + * + * Registering another view with a name that is already registered + * overwrites the old registration of the same type. + * + * @param viewName + * String that identifies a view (not null nor empty string) + * @param view + * {@link View} instance (not null) + */ + public void addView(String viewName, View view) { + + // Check parameters + if (viewName == null || view == null) { + throw new IllegalArgumentException( + "view and viewName must be non-null"); + } + + removeView(viewName); + registerProvider(new StaticViewProvider(viewName, view)); + } + + /** + * Register for a view name a view class. + * + * Registering another view with a name that is already registered + * overwrites the old registration of the same type. + * + * A new view instance is created every time a view is requested. + * + * @param viewName + * String that identifies a view (not null nor empty string) + * @param viewClass + * {@link View} class to instantiate when a view is requested + * (not null) + */ + public void addView(String viewName, Class<? extends View> viewClass) { + + // Check parameters + if (viewName == null || viewClass == null) { + throw new IllegalArgumentException( + "view and viewClass must be non-null"); + } + + removeView(viewName); + registerProvider(new ClassBasedViewProvider(viewName, viewClass)); + } + + /** + * Remove view from navigator. + * + * This method only applies to views registered using + * {@link #addView(String, View)} or {@link #addView(String, Class)}. + * + * @param viewName + * name of the view to remove + */ + public void removeView(String viewName) { + Iterator<ViewProvider> it = providers.iterator(); + while (it.hasNext()) { + ViewProvider provider = it.next(); + if (provider instanceof StaticViewProvider) { + StaticViewProvider staticProvider = (StaticViewProvider) provider; + if (staticProvider.getViewName().equals(viewName)) { + it.remove(); + } + } else if (provider instanceof ClassBasedViewProvider) { + ClassBasedViewProvider classBasedProvider = (ClassBasedViewProvider) provider; + if (classBasedProvider.getViewName().equals(viewName)) { + it.remove(); + } + } + } + } + + /** + * Register a view provider (factory). + * + * Providers are called in order of registration until one that can handle + * the requested view name is found. + * + * @param provider + * provider to register + */ + public void registerProvider(ViewProvider provider) { + providers.add(provider); + } + + /** + * Unregister a view provider (factory). + * + * @param provider + * provider to unregister + */ + public void unregisterProvider(ViewProvider provider) { + providers.remove(provider); + } + + /** + * Listen to changes of the active view. + * + * The listener will get notified after the view has changed. + * + * @param listener + * Listener to invoke after view changes. + */ + public void addListener(ViewChangeListener listener) { + listeners.add(listener); + } + + /** + * Remove a view change listener. + * + * @param listener + * Listener to remove. + */ + public void removeListener(ViewChangeListener listener) { + listeners.remove(listener); + } + +} diff --git a/src/com/vaadin/navigator/View.java b/src/com/vaadin/navigator/View.java new file mode 100644 index 0000000000..4d135b4c0b --- /dev/null +++ b/src/com/vaadin/navigator/View.java @@ -0,0 +1,36 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.navigator; + +import java.io.Serializable; + +import com.vaadin.ui.Component; + +/** + * Interface for all views controlled by the navigator. + * + * Each view added to the navigator must implement this interface. Typically, a + * view is a {@link Component}. + * + * @author Vaadin Ltd + * @since 7.0 + */ +public interface View extends Serializable { + + /** + * This view is navigated to. + * + * This method is always called before the view is shown on screen. If there + * is any additional id to data what should be shown in the view, it is also + * optionally passed as parameter. + * + * TODO fragmentParameters null if no parameters or empty string? + * + * @param fragmentParameters + * parameters to the view or null if none given. This is the + * string that appears e.g. in URI after "viewname/" + */ + public void navigateTo(String fragmentParameters); +}
\ No newline at end of file diff --git a/src/com/vaadin/navigator/ViewChangeListener.java b/src/com/vaadin/navigator/ViewChangeListener.java new file mode 100644 index 0000000000..2eb34e6fcf --- /dev/null +++ b/src/com/vaadin/navigator/ViewChangeListener.java @@ -0,0 +1,118 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.navigator; + +import java.io.Serializable; +import java.util.EventObject; + +/** + * Interface for listening to View changes before and after they occur. + * + * Implementations of this interface can also block navigation between views + * before it is performed. + * + * @author Vaadin Ltd + * @since 7.0 + */ +public interface ViewChangeListener extends Serializable { + + /** + * Event received by the listener for attempted and executed view changes. + */ + public static class ViewChangeEvent extends EventObject { + private final View oldView; + private final View newView; + private final String viewName; + private final String fragmentParameters; + + /** + * Create a new view change event. + * + * @param navigator + * Navigator that triggered the event, not null + */ + public ViewChangeEvent(Navigator navigator, View oldView, View newView, + String viewName, String fragmentParameters) { + super(navigator); + this.oldView = oldView; + this.newView = newView; + this.viewName = viewName; + this.fragmentParameters = fragmentParameters; + } + + /** + * Returns the navigator that triggered this event. + * + * @return Navigator (not null) + */ + public Navigator getNavigator() { + return (Navigator) getSource(); + } + + /** + * Returns the view being deactivated. + * + * @return old View + */ + public View getOldView() { + return oldView; + } + + /** + * Returns the view being activated. + * + * @return new View + */ + public View getNewView() { + return newView; + } + + /** + * Returns the view name of the view being activated. + * + * @return view name of the new View + */ + public String getViewName() { + return viewName; + } + + /** + * Returns the parameters for the view being activated. + * + * @return fragment parameters (potentially bookmarkable) for the new + * view + */ + public String getFragmentParameters() { + return fragmentParameters; + } + } + + /** + * Check whether changing the view is permissible. + * + * This method may also e.g. open a "save" dialog or question about the + * change, which may re-initiate the navigation operation after user action. + * + * If this listener does not want to block the view change (e.g. does not + * know the view in question), it should return true. If any listener + * returns false, the view change is not allowed. + * + * @param event + * view change event + * @return true if the view change should be allowed or this listener does + * not care about the view change, false to block the change + */ + public boolean isViewChangeAllowed(ViewChangeEvent event); + + /** + * Invoked after the view has changed. Be careful for deadlocks if you + * decide to change the view again in the listener. + * + * @param event + * view change event + */ + public void navigatorViewChanged(ViewChangeEvent event); + +}
\ No newline at end of file diff --git a/src/com/vaadin/navigator/ViewDisplay.java b/src/com/vaadin/navigator/ViewDisplay.java new file mode 100644 index 0000000000..6016951394 --- /dev/null +++ b/src/com/vaadin/navigator/ViewDisplay.java @@ -0,0 +1,29 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.navigator; + +import java.io.Serializable; + +/** + * Interface for displaying a view in an appropriate location. + * + * The view display can be a component/layout itself or can modify a separate + * layout. + * + * @author Vaadin Ltd + * @since 7.0 + */ +public interface ViewDisplay extends Serializable { + /** + * Remove previously shown view and show the newly selected view in its + * place. + * + * The parameters for the view have been set before this method is called. + * + * @param view + * new view to show + */ + public void showView(View view); +}
\ No newline at end of file diff --git a/src/com/vaadin/navigator/ViewProvider.java b/src/com/vaadin/navigator/ViewProvider.java new file mode 100644 index 0000000000..4d9d22acab --- /dev/null +++ b/src/com/vaadin/navigator/ViewProvider.java @@ -0,0 +1,44 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.navigator; + +import java.io.Serializable; + +/** + * A provider for view instances that can return pre-registered views or + * dynamically create new views. + * + * If multiple providers are used, {@link #getViewName(String)} of each is + * called (in registration order) until one of them returns a non-null value. + * The {@link #getView(String)} method of that provider is then used. + * + * @author Vaadin Ltd + * @since 7.0 + */ +public interface ViewProvider extends Serializable { + /** + * Extract the view name from a combined view name and parameter string. + * This method should return a view name if and only if this provider + * handles creation of such views. + * + * @param viewAndParameters + * string with view name and its fragment parameters (if given), + * not null + * @return view name if the view is handled by this provider, null otherwise + */ + public String getViewName(String viewAndParameters); + + /** + * Create or return a pre-created instance of a view. + * + * The parameters for the view are set separately by the navigator when the + * view is activated. + * + * @param viewName + * name of the view, not null + * @return newly created view (null if none available for the view name) + */ + public View getView(String viewName); +}
\ No newline at end of file diff --git a/src/com/vaadin/terminal/AbstractClientConnector.java b/src/com/vaadin/terminal/AbstractClientConnector.java new file mode 100644 index 0000000000..9de444d70e --- /dev/null +++ b/src/com/vaadin/terminal/AbstractClientConnector.java @@ -0,0 +1,490 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal; + +import java.io.Serializable; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.logging.Logger; + +import com.vaadin.Application; +import com.vaadin.terminal.gwt.client.communication.ClientRpc; +import com.vaadin.terminal.gwt.client.communication.ServerRpc; +import com.vaadin.terminal.gwt.client.communication.SharedState; +import com.vaadin.terminal.gwt.server.ClientConnector; +import com.vaadin.terminal.gwt.server.ClientMethodInvocation; +import com.vaadin.terminal.gwt.server.RpcManager; +import com.vaadin.terminal.gwt.server.RpcTarget; +import com.vaadin.terminal.gwt.server.ServerRpcManager; +import com.vaadin.ui.HasComponents; +import com.vaadin.ui.Root; + +/** + * An abstract base class for ClientConnector implementations. This class + * provides all the basic functionality required for connectors. + * + * @author Vaadin Ltd + * @version @VERSION@ + * @since 7.0.0 + */ +public abstract class AbstractClientConnector implements ClientConnector { + /** + * A map from client to server RPC interface class to the RPC call manager + * that handles incoming RPC calls for that interface. + */ + private Map<Class<?>, RpcManager> rpcManagerMap = new HashMap<Class<?>, RpcManager>(); + + /** + * A map from server to client RPC interface class to the RPC proxy that + * sends ourgoing RPC calls for that interface. + */ + private Map<Class<?>, ClientRpc> rpcProxyMap = new HashMap<Class<?>, ClientRpc>(); + + /** + * Shared state object to be communicated from the server to the client when + * modified. + */ + private SharedState sharedState; + + /** + * Pending RPC method invocations to be sent. + */ + private ArrayList<ClientMethodInvocation> pendingInvocations = new ArrayList<ClientMethodInvocation>(); + + private String connectorId; + + private ArrayList<Extension> extensions = new ArrayList<Extension>(); + + private ClientConnector parent; + + /* Documentation copied from interface */ + public void requestRepaint() { + Root root = getRoot(); + if (root != null) { + root.getConnectorTracker().markDirty(this); + } + } + + /** + * Registers an RPC interface implementation for this component. + * + * A component can listen to multiple RPC interfaces, and subclasses can + * register additional implementations. + * + * @since 7.0 + * + * @param implementation + * RPC interface implementation + * @param rpcInterfaceType + * RPC interface class for which the implementation should be + * registered + */ + protected <T> void registerRpc(T implementation, Class<T> rpcInterfaceType) { + rpcManagerMap.put(rpcInterfaceType, new ServerRpcManager<T>( + implementation, rpcInterfaceType)); + } + + /** + * Registers an RPC interface implementation for this component. + * + * A component can listen to multiple RPC interfaces, and subclasses can + * register additional implementations. + * + * @since 7.0 + * + * @param implementation + * RPC interface implementation. Also used to deduce the type. + */ + protected <T extends ServerRpc> void registerRpc(T implementation) { + Class<?> cls = implementation.getClass(); + Class<?>[] interfaces = cls.getInterfaces(); + while (interfaces.length == 0) { + // Search upwards until an interface is found. It must be found as T + // extends ServerRpc + cls = cls.getSuperclass(); + interfaces = cls.getInterfaces(); + } + if (interfaces.length != 1 + || !(ServerRpc.class.isAssignableFrom(interfaces[0]))) { + throw new RuntimeException( + "Use registerRpc(T implementation, Class<T> rpcInterfaceType) if the Rpc implementation implements more than one interface"); + } + @SuppressWarnings("unchecked") + Class<T> type = (Class<T>) interfaces[0]; + registerRpc(implementation, type); + } + + public SharedState getState() { + if (null == sharedState) { + sharedState = createState(); + } + return sharedState; + } + + /** + * Creates the shared state bean to be used in server to client + * communication. + * <p> + * By default a state object of the defined return type of + * {@link #getState()} is created. Subclasses can override this method and + * return a new instance of the correct state class but this should rarely + * be necessary. + * </p> + * <p> + * No configuration of the values of the state should be performed in + * {@link #createState()}. + * + * @since 7.0 + * + * @return new shared state object + */ + protected SharedState createState() { + try { + return getStateType().newInstance(); + } catch (Exception e) { + throw new RuntimeException( + "Error creating state of type " + getStateType().getName() + + " for " + getClass().getName(), e); + } + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.terminal.gwt.server.ClientConnector#getStateType() + */ + public Class<? extends SharedState> getStateType() { + try { + Method m = getClass().getMethod("getState", (Class[]) null); + Class<?> type = m.getReturnType(); + return type.asSubclass(SharedState.class); + } catch (Exception e) { + throw new RuntimeException("Error finding state type for " + + getClass().getName(), e); + } + } + + /** + * Returns an RPC proxy for a given server to client RPC interface for this + * component. + * + * TODO more javadoc, subclasses, ... + * + * @param rpcInterface + * RPC interface type + * + * @since 7.0 + */ + public <T extends ClientRpc> T getRpcProxy(final Class<T> rpcInterface) { + // create, initialize and return a dynamic proxy for RPC + try { + if (!rpcProxyMap.containsKey(rpcInterface)) { + Class<?> proxyClass = Proxy.getProxyClass( + rpcInterface.getClassLoader(), rpcInterface); + Constructor<?> constructor = proxyClass + .getConstructor(InvocationHandler.class); + T rpcProxy = rpcInterface.cast(constructor + .newInstance(new RpcInvoicationHandler(rpcInterface))); + // cache the proxy + rpcProxyMap.put(rpcInterface, rpcProxy); + } + return (T) rpcProxyMap.get(rpcInterface); + } catch (Exception e) { + // TODO exception handling? + throw new RuntimeException(e); + } + } + + private static final class AllChildrenIterable implements + Iterable<ClientConnector>, Serializable { + private final ClientConnector connector; + + private AllChildrenIterable(ClientConnector connector) { + this.connector = connector; + } + + public Iterator<ClientConnector> iterator() { + CombinedIterator<ClientConnector> iterator = new CombinedIterator<ClientConnector>(); + iterator.addIterator(connector.getExtensions().iterator()); + + if (connector instanceof HasComponents) { + HasComponents hasComponents = (HasComponents) connector; + iterator.addIterator(hasComponents.iterator()); + } + + return iterator; + } + } + + private class RpcInvoicationHandler implements InvocationHandler, + Serializable { + + private String rpcInterfaceName; + + public RpcInvoicationHandler(Class<?> rpcInterface) { + rpcInterfaceName = rpcInterface.getName().replaceAll("\\$", "."); + } + + public Object invoke(Object proxy, Method method, Object[] args) + throws Throwable { + addMethodInvocationToQueue(rpcInterfaceName, method, args); + // TODO no need to do full repaint if only RPC calls + requestRepaint(); + return null; + } + + } + + /** + * For internal use: adds a method invocation to the pending RPC call queue. + * + * @param interfaceName + * RPC interface name + * @param method + * RPC method + * @param parameters + * RPC all parameters + * + * @since 7.0 + */ + protected void addMethodInvocationToQueue(String interfaceName, + Method method, Object[] parameters) { + // add to queue + pendingInvocations.add(new ClientMethodInvocation(this, interfaceName, + method, parameters)); + } + + /** + * @see RpcTarget#getRpcManager(Class) + * + * @param rpcInterface + * RPC interface for which a call was made + * @return RPC Manager handling calls for the interface + * + * @since 7.0 + */ + public RpcManager getRpcManager(Class<?> rpcInterface) { + return rpcManagerMap.get(rpcInterface); + } + + public List<ClientMethodInvocation> retrievePendingRpcCalls() { + if (pendingInvocations.isEmpty()) { + return Collections.emptyList(); + } else { + List<ClientMethodInvocation> result = pendingInvocations; + pendingInvocations = new ArrayList<ClientMethodInvocation>(); + return Collections.unmodifiableList(result); + } + } + + public String getConnectorId() { + if (connectorId == null) { + if (getApplication() == null) { + throw new RuntimeException( + "Component must be attached to an application when getConnectorId() is called for the first time"); + } + connectorId = getApplication().createConnectorId(this); + } + return connectorId; + } + + /** + * Finds the Application to which this connector belongs. If the connector + * has not been attached, <code>null</code> is returned. + * + * @return The connector's application, or <code>null</code> if not attached + */ + protected Application getApplication() { + Root root = getRoot(); + if (root == null) { + return null; + } else { + return root.getApplication(); + } + } + + /** + * Finds a Root ancestor of this connector. <code>null</code> is returned if + * no Root ancestor is found (typically because the connector is not + * attached to a proper hierarchy). + * + * @return the Root ancestor of this connector, or <code>null</code> if none + * is found. + */ + protected Root getRoot() { + ClientConnector connector = this; + while (connector != null) { + if (connector instanceof Root) { + return (Root) connector; + } + connector = connector.getParent(); + } + return null; + } + + private static Logger getLogger() { + return Logger.getLogger(AbstractClientConnector.class.getName()); + } + + public void requestRepaintAll() { + requestRepaint(); + + for (ClientConnector connector : getAllChildrenIterable(this)) { + connector.requestRepaintAll(); + } + } + + private static final class CombinedIterator<T> implements Iterator<T>, + Serializable { + + private final Collection<Iterator<? extends T>> iterators = new ArrayList<Iterator<? extends T>>(); + + public void addIterator(Iterator<? extends T> iterator) { + iterators.add(iterator); + } + + public boolean hasNext() { + for (Iterator<? extends T> i : iterators) { + if (i.hasNext()) { + return true; + } + } + return false; + } + + public T next() { + for (Iterator<? extends T> i : iterators) { + if (i.hasNext()) { + return i.next(); + } + } + throw new NoSuchElementException(); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + } + + /** + * Get an Iterable for iterating over all child connectors, including both + * extensions and child components. + * + * @param connector + * the connector to get children for + * @return an Iterable giving all child connectors. + */ + public static Iterable<ClientConnector> getAllChildrenIterable( + final ClientConnector connector) { + return new AllChildrenIterable(connector); + } + + public Collection<Extension> getExtensions() { + return Collections.unmodifiableCollection(extensions); + } + + /** + * Add an extension to this connector. This method is protected to allow + * extensions to select which targets they can extend. + * + * @param extension + * the extension to add + */ + protected void addExtension(Extension extension) { + ClientConnector previousParent = extension.getParent(); + if (previousParent == this) { + // Nothing to do, already attached + return; + } else if (previousParent != null) { + throw new IllegalStateException( + "Moving an extension from one parent to another is not supported"); + } + + extensions.add(extension); + extension.setParent(this); + requestRepaint(); + } + + public void removeExtension(Extension extension) { + extension.setParent(null); + extensions.remove(extension); + requestRepaint(); + } + + public void setParent(ClientConnector parent) { + + // If the parent is not changed, don't do anything + if (parent == this.parent) { + return; + } + + if (parent != null && this.parent != null) { + throw new IllegalStateException(getClass().getName() + + " already has a parent."); + } + + // Send detach event if the component have been connected to a window + if (getApplication() != null) { + detach(); + } + + // Connect to new parent + this.parent = parent; + + // Send attach event if connected to an application + if (getApplication() != null) { + attach(); + } + } + + public ClientConnector getParent() { + return parent; + } + + public void attach() { + requestRepaint(); + + getRoot().getConnectorTracker().registerConnector(this); + + for (ClientConnector connector : getAllChildrenIterable(this)) { + connector.attach(); + } + + } + + /** + * {@inheritDoc} + * + * <p> + * The {@link #getApplication()} and {@link #getRoot()} methods might return + * <code>null</code> after this method is called. + * </p> + */ + public void detach() { + for (ClientConnector connector : getAllChildrenIterable(this)) { + connector.detach(); + } + + getRoot().getConnectorTracker().unregisterConnector(this); + } + + public boolean isConnectorEnabled() { + if (getParent() == null) { + // No parent -> the component cannot receive updates from the client + return false; + } else { + return getParent().isConnectorEnabled(); + } + } +} diff --git a/src/com/vaadin/terminal/AbstractErrorMessage.java b/src/com/vaadin/terminal/AbstractErrorMessage.java index 1a625fc0e6..3f526f7339 100644 --- a/src/com/vaadin/terminal/AbstractErrorMessage.java +++ b/src/com/vaadin/terminal/AbstractErrorMessage.java @@ -4,6 +4,8 @@ package com.vaadin.terminal; +import java.io.PrintWriter; +import java.io.StringWriter; import java.util.ArrayList; import java.util.List; @@ -156,7 +158,10 @@ public abstract class AbstractErrorMessage implements ErrorMessage { } return error; } else { - return new SystemError(t); + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + t.printStackTrace(pw); + return new SystemError(sw.toString()); } } diff --git a/src/com/vaadin/terminal/AbstractExtension.java b/src/com/vaadin/terminal/AbstractExtension.java new file mode 100644 index 0000000000..33a60e39ef --- /dev/null +++ b/src/com/vaadin/terminal/AbstractExtension.java @@ -0,0 +1,76 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal; + +import com.vaadin.terminal.gwt.server.ClientConnector; + +/** + * An extension is an entity that is attached to a Component or another + * Extension and independently communicates between client and server. + * <p> + * Extensions can use shared state and RPC in the same way as components. + * <p> + * AbstractExtension adds a mechanism for adding the extension to any Connector + * (extend). To let the Extension determine what kind target it can be added to, + * the extend method is declared as protected. + * + * @author Vaadin Ltd + * @version @VERSION@ + * @since 7.0.0 + */ +public abstract class AbstractExtension extends AbstractClientConnector + implements Extension { + private boolean previouslyAttached = false; + + /** + * Gets a type that the parent must be an instance of. Override this if the + * extension only support certain targets, e.g. if only TextFields can be + * extended. + * + * @return a type that the parent must be an instance of + */ + protected Class<? extends ClientConnector> getSupportedParentType() { + return ClientConnector.class; + } + + /** + * Add this extension to the target connector. This method is protected to + * allow subclasses to require a more specific type of target. + * + * @param target + * the connector to attach this extension to + */ + protected void extend(AbstractClientConnector target) { + target.addExtension(this); + } + + /** + * Remove this extension from its target. After an extension has been + * removed, it can not be attached again. + */ + public void removeFromTarget() { + getParent().removeExtension(this); + } + + @Override + public void setParent(ClientConnector parent) { + if (previouslyAttached && parent != null) { + throw new IllegalStateException( + "An extension can not be set to extend a new target after getting detached from the previous."); + } + + Class<? extends ClientConnector> supportedParentType = getSupportedParentType(); + if (parent == null || supportedParentType.isInstance(parent)) { + super.setParent(parent); + previouslyAttached = true; + } else { + throw new IllegalArgumentException(getClass().getName() + + " can only be attached to targets of type " + + supportedParentType.getName() + " but attach to " + + parent.getClass().getName() + " was attempted."); + } + } + +} diff --git a/src/com/vaadin/terminal/AbstractJavaScriptExtension.java b/src/com/vaadin/terminal/AbstractJavaScriptExtension.java new file mode 100644 index 0000000000..bdcd948c74 --- /dev/null +++ b/src/com/vaadin/terminal/AbstractJavaScriptExtension.java @@ -0,0 +1,158 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal; + +import com.vaadin.terminal.gwt.client.JavaScriptExtensionState; +import com.vaadin.ui.JavaScriptCallback; + +/** + * Base class for Extensions with all client-side logic implemented using + * JavaScript. + * <p> + * When a new JavaScript extension is initialized in the browser, the framework + * will look for a globally defined JavaScript function that will initialize the + * extension. The name of the initialization function is formed by replacing . + * with _ in the name of the server-side class. If no such function is defined, + * each super class is used in turn until a match is found. The framework will + * thus first attempt with <code>com_example_MyExtension</code> for the + * server-side + * <code>com.example.MyExtension extends AbstractJavaScriptExtension</code> + * class. If MyExtension instead extends <code>com.example.SuperExtension</code> + * , then <code>com_example_SuperExtension</code> will also be attempted if + * <code>com_example_MyExtension</code> has not been defined. + * <p> + * + * The initialization function will be called with <code>this</code> pointing to + * a connector wrapper object providing integration to Vaadin with the following + * functions: + * <ul> + * <li><code>getConnectorId()</code> - returns a string with the id of the + * connector.</li> + * <li><code>getParentId([connectorId])</code> - returns a string with the id of + * the connector's parent. If <code>connectorId</code> is provided, the id of + * the parent of the corresponding connector with the passed id is returned + * instead.</li> + * <li><code>getWidgetElement([connectorId])</code> - returns the DOM Element + * that is the root of a connector's widget. <code>null</code> is returned if + * the connector can not be found or if the connector doesn't have a widget. If + * <code>connectorId</code> is not provided, the connector id of the current + * connector will be used.</li> + * <li><code>getState()</code> - returns an object corresponding to the shared + * state defined on the server. The scheme for conversion between Java and + * JavaScript types is described bellow.</li> + * <li><code>registerRpc([name, ] rpcObject)</code> - registers the + * <code>rpcObject</code> as a RPC handler. <code>rpcObject</code> should be an + * object with field containing functions for all eligible RPC functions. If + * <code>name</code> is provided, the RPC handler will only used for RPC calls + * for the RPC interface with the same fully qualified Java name. If no + * <code>name</code> is provided, the RPC handler will be used for all incoming + * RPC invocations where the RPC method name is defined as a function field in + * the handler. The scheme for conversion between Java types in the RPC + * interface definition and the JavaScript values passed as arguments to the + * handler functions is described bellow.</li> + * <li><code>getRpcProxy([name])</code> - returns an RPC proxy object. If + * <code>name</code> is provided, the proxy object will contain functions for + * all methods in the RPC interface with the same fully qualified name, provided + * a RPC handler has been registered by the server-side code. If no + * <code>name</code> is provided, the returned RPC proxy object will contain + * functions for all methods in all RPC interfaces registered for the connector + * on the server. If the same method name is present in multiple registered RPC + * interfaces, the corresponding function in the RPC proxy object will throw an + * exception when called. The scheme for conversion between Java types in the + * RPC interface and the JavaScript values that should be passed to the + * functions is described bellow.</li> + * </ul> + * The connector wrapper also supports these special functions: + * <ul> + * <li><code>onStateChange</code> - If the JavaScript code assigns a function to + * the field, that function is called whenever the contents of the shared state + * is changed.</li> + * <li>Any field name corresponding to a call to + * {@link #registerCallback(String, JavaScriptCallback)} on the server will + * automatically be present as a function that triggers the registered callback + * on the server.</li> + * <li>Any field name referred to using + * {@link #invokeCallback(String, Object...)} on the server will be called if a + * function has been assigned to the field.</li> + * </ul> + * <p> + * + * Values in the Shared State and in RPC calls are converted between Java and + * JavaScript using the following conventions: + * <ul> + * <li>Primitive Java numbers (byte, char, int, long, float, double) and their + * boxed types (Byte, Character, Integer, Long, Float, Double) are represented + * by JavaScript numbers.</li> + * <li>The primitive Java boolean and the boxed Boolean are represented by + * JavaScript booleans.</li> + * <li>Java Strings are represented by JavaScript strings.</li> + * <li>List, Set and all arrays in Java are represented by JavaScript arrays.</li> + * <li>Map<String, ?> in Java is represented by JavaScript object with fields + * corresponding to the map keys.</li> + * <li>Any other Java Map is represented by a JavaScript array containing two + * arrays, the first contains the keys and the second contains the values in the + * same order.</li> + * <li>A Java Bean is represented by a JavaScript object with fields + * corresponding to the bean's properties.</li> + * <li>A Java Connector is represented by a JavaScript string containing the + * connector's id.</li> + * <li>A pluggable serialization mechanism is provided for types not described + * here. Please refer to the documentation for specific types for serialization + * information.</li> + * </ul> + * + * @author Vaadin Ltd + * @version @VERSION@ + * @since 7.0.0 + */ +public abstract class AbstractJavaScriptExtension extends AbstractExtension { + private JavaScriptCallbackHelper callbackHelper = new JavaScriptCallbackHelper( + this); + + @Override + protected <T> void registerRpc(T implementation, Class<T> rpcInterfaceType) { + super.registerRpc(implementation, rpcInterfaceType); + callbackHelper.registerRpc(rpcInterfaceType); + } + + /** + * Register a {@link JavaScriptCallback} that can be called from the + * JavaScript using the provided name. A JavaScript function with the + * provided name will be added to the connector wrapper object (initially + * available as <code>this</code>). Calling that JavaScript function will + * cause the call method in the registered {@link JavaScriptCallback} to be + * invoked with the same arguments. + * + * @param functionName + * the name that should be used for client-side callback + * @param javaScriptCallback + * the callback object that will be invoked when the JavaScript + * function is called + */ + protected void registerCallback(String functionName, + JavaScriptCallback javaScriptCallback) { + callbackHelper.registerCallback(functionName, javaScriptCallback); + } + + /** + * Invoke a named function that the connector JavaScript has added to the + * JavaScript connector wrapper object. The arguments should only contain + * data types that can be represented in JavaScript, including primitive + * boxing types, arrays, String, List, Set, Map, Connector and JavaBeans. + * + * @param name + * the name of the function + * @param arguments + * function arguments + */ + protected void invokeCallback(String name, Object... arguments) { + callbackHelper.invokeCallback(name, arguments); + } + + @Override + public JavaScriptExtensionState getState() { + return (JavaScriptExtensionState) super.getState(); + } +} diff --git a/src/com/vaadin/terminal/ClassResource.java b/src/com/vaadin/terminal/ClassResource.java index fa196e90d9..e7419576f1 100644 --- a/src/com/vaadin/terminal/ClassResource.java +++ b/src/com/vaadin/terminal/ClassResource.java @@ -60,13 +60,7 @@ public class ClassResource implements ApplicationResource, Serializable { * 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); + this(application.getClass(), resourceName, application); } /** diff --git a/src/com/vaadin/terminal/CombinedRequest.java b/src/com/vaadin/terminal/CombinedRequest.java index ccef6d8963..abf5e0412a 100644 --- a/src/com/vaadin/terminal/CombinedRequest.java +++ b/src/com/vaadin/terminal/CombinedRequest.java @@ -129,7 +129,7 @@ public class CombinedRequest implements WrappedRequest { public WebBrowser getWebBrowser() { WebApplicationContext context = (WebApplicationContext) Application - .getCurrentApplication().getContext(); + .getCurrent().getContext(); return context.getBrowser(); } }; diff --git a/src/com/vaadin/terminal/Extension.java b/src/com/vaadin/terminal/Extension.java new file mode 100644 index 0000000000..ef5bb4cf8d --- /dev/null +++ b/src/com/vaadin/terminal/Extension.java @@ -0,0 +1,27 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal; + +import com.vaadin.terminal.gwt.server.ClientConnector; + +/** + * An extension is an entity that is attached to a Component or another + * Extension and independently communicates between client and server. + * <p> + * An extension can only be attached once. It is not supported to move an + * extension from one target to another. + * <p> + * Extensions can use shared state and RPC in the same way as components. + * + * @author Vaadin Ltd + * @version @VERSION@ + * @since 7.0.0 + */ +public interface Extension extends ClientConnector { + /* + * Currently just an empty marker interface to distinguish between + * extensions and other connectors, e.g. components + */ +} diff --git a/src/com/vaadin/terminal/JavaScriptCallbackHelper.java b/src/com/vaadin/terminal/JavaScriptCallbackHelper.java new file mode 100644 index 0000000000..01db0267d9 --- /dev/null +++ b/src/com/vaadin/terminal/JavaScriptCallbackHelper.java @@ -0,0 +1,115 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal; + +import java.io.Serializable; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import com.vaadin.external.json.JSONArray; +import com.vaadin.external.json.JSONException; +import com.vaadin.terminal.gwt.client.JavaScriptConnectorHelper; +import com.vaadin.terminal.gwt.client.JavaScriptConnectorHelper.JavaScriptConnectorState; +import com.vaadin.tools.ReflectTools; +import com.vaadin.ui.AbstractJavaScriptComponent; +import com.vaadin.ui.JavaScript.JavaScriptCallbackRpc; +import com.vaadin.ui.JavaScriptCallback; + +/** + * Internal helper class used to implement functionality common to + * {@link AbstractJavaScriptComponent} and {@link AbstractJavaScriptExtension}. + * Corresponding support in client-side code is in + * {@link JavaScriptConnectorHelper}. + * <p> + * You should most likely no use this class directly. + * + * @author Vaadin Ltd + * @version @VERSION@ + * @since 7.0.0 + */ +public class JavaScriptCallbackHelper implements Serializable { + + private static final Method CALL_METHOD = ReflectTools.findMethod( + JavaScriptCallbackRpc.class, "call", String.class, JSONArray.class); + private AbstractClientConnector connector; + + private Map<String, JavaScriptCallback> callbacks = new HashMap<String, JavaScriptCallback>(); + private JavaScriptCallbackRpc javascriptCallbackRpc; + + public JavaScriptCallbackHelper(AbstractClientConnector connector) { + this.connector = connector; + } + + public void registerCallback(String functionName, + JavaScriptCallback javaScriptCallback) { + callbacks.put(functionName, javaScriptCallback); + JavaScriptConnectorState state = getConnectorState(); + if (state.getCallbackNames().add(functionName)) { + connector.requestRepaint(); + } + ensureRpc(); + } + + private JavaScriptConnectorState getConnectorState() { + JavaScriptConnectorState state = (JavaScriptConnectorState) connector + .getState(); + return state; + } + + private void ensureRpc() { + if (javascriptCallbackRpc == null) { + javascriptCallbackRpc = new JavaScriptCallbackRpc() { + public void call(String name, JSONArray arguments) { + JavaScriptCallback callback = callbacks.get(name); + try { + callback.call(arguments); + } catch (JSONException e) { + throw new IllegalArgumentException(e); + } + } + }; + connector.registerRpc(javascriptCallbackRpc); + } + } + + public void invokeCallback(String name, Object... arguments) { + if (callbacks.containsKey(name)) { + throw new IllegalStateException( + "Can't call callback " + + name + + " on the client because a callback with the same name is registered on the server."); + } + JSONArray args = new JSONArray(Arrays.asList(arguments)); + connector.addMethodInvocationToQueue( + JavaScriptCallbackRpc.class.getName(), CALL_METHOD, + new Object[] { name, args }); + connector.requestRepaint(); + } + + public void registerRpc(Class<?> rpcInterfaceType) { + if (rpcInterfaceType == JavaScriptCallbackRpc.class) { + // Ignore + return; + } + Map<String, Set<String>> rpcInterfaces = getConnectorState() + .getRpcInterfaces(); + String interfaceName = rpcInterfaceType.getName(); + if (!rpcInterfaces.containsKey(interfaceName)) { + Set<String> methodNames = new HashSet<String>(); + + for (Method method : rpcInterfaceType.getMethods()) { + methodNames.add(method.getName()); + } + + rpcInterfaces.put(interfaceName, methodNames); + connector.requestRepaint(); + } + } + +} diff --git a/src/com/vaadin/terminal/Page.java b/src/com/vaadin/terminal/Page.java new file mode 100644 index 0000000000..8ccb243a1e --- /dev/null +++ b/src/com/vaadin/terminal/Page.java @@ -0,0 +1,646 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal; + +import java.io.Serializable; +import java.lang.reflect.Method; +import java.util.EventObject; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import com.vaadin.event.EventRouter; +import com.vaadin.terminal.WrappedRequest.BrowserDetails; +import com.vaadin.terminal.gwt.client.ui.notification.VNotification; +import com.vaadin.terminal.gwt.client.ui.root.PageClientRpc; +import com.vaadin.terminal.gwt.client.ui.root.VRoot; +import com.vaadin.terminal.gwt.server.WebApplicationContext; +import com.vaadin.terminal.gwt.server.WebBrowser; +import com.vaadin.tools.ReflectTools; +import com.vaadin.ui.JavaScript; +import com.vaadin.ui.Notification; +import com.vaadin.ui.Root; + +public class Page implements Serializable { + + /** + * Listener that gets notified when the size of the browser window + * containing the root has changed. + * + * @see Root#addListener(BrowserWindowResizeListener) + */ + public interface BrowserWindowResizeListener extends Serializable { + /** + * Invoked when the browser window containing a Root has been resized. + * + * @param event + * a browser window resize event + */ + public void browserWindowResized(BrowserWindowResizeEvent event); + } + + /** + * Event that is fired when a browser window containing a root is resized. + */ + public class BrowserWindowResizeEvent extends EventObject { + + private final int width; + private final int height; + + /** + * Creates a new event + * + * @param source + * the root for which the browser window has been resized + * @param width + * the new width of the browser window + * @param height + * the new height of the browser window + */ + public BrowserWindowResizeEvent(Page source, int width, int height) { + super(source); + this.width = width; + this.height = height; + } + + @Override + public Page getSource() { + return (Page) super.getSource(); + } + + /** + * Gets the new browser window height + * + * @return an integer with the new pixel height of the browser window + */ + public int getHeight() { + return height; + } + + /** + * Gets the new browser window width + * + * @return an integer with the new pixel width of the browser window + */ + public int getWidth() { + return width; + } + } + + /** + * Private class for storing properties related to opening resources. + */ + private class OpenResource implements Serializable { + + /** + * The resource to open + */ + private final Resource resource; + + /** + * The name of the target window + */ + private final String name; + + /** + * The width of the target window + */ + private final int width; + + /** + * The height of the target window + */ + private final int height; + + /** + * The border style of the target window + */ + private final int border; + + /** + * Creates a new open resource. + * + * @param resource + * The resource to open + * @param name + * The name of the target window + * @param width + * The width of the target window + * @param height + * The height of the target window + * @param border + * The border style of the target window + */ + private OpenResource(Resource resource, String name, int width, + int height, int border) { + this.resource = resource; + this.name = name; + this.width = width; + this.height = height; + this.border = border; + } + + /** + * Paints the open request. Should be painted inside the window. + * + * @param target + * the paint target + * @throws PaintException + * if the paint operation fails + */ + private void paintContent(PaintTarget target) throws PaintException { + target.startTag("open"); + target.addAttribute("src", resource); + if (name != null && name.length() > 0) { + target.addAttribute("name", name); + } + if (width >= 0) { + target.addAttribute("width", width); + } + if (height >= 0) { + target.addAttribute("height", height); + } + switch (border) { + case BORDER_MINIMAL: + target.addAttribute("border", "minimal"); + break; + case BORDER_NONE: + target.addAttribute("border", "none"); + break; + } + + target.endTag("open"); + } + } + + private static final Method BROWSWER_RESIZE_METHOD = ReflectTools + .findMethod(BrowserWindowResizeListener.class, + "browserWindowResized", BrowserWindowResizeEvent.class); + + /** + * A border style used for opening resources in a window without a border. + */ + public static final int BORDER_NONE = 0; + + /** + * A border style used for opening resources in a window with a minimal + * border. + */ + public static final int BORDER_MINIMAL = 1; + + /** + * A border style that indicates that the default border style should be + * used when opening resources. + */ + public static final int BORDER_DEFAULT = 2; + + /** + * Listener that listens changes in URI fragment. + */ + public interface FragmentChangedListener extends Serializable { + public void fragmentChanged(FragmentChangedEvent event); + } + + private static final Method FRAGMENT_CHANGED_METHOD = ReflectTools + .findMethod(Page.FragmentChangedListener.class, "fragmentChanged", + FragmentChangedEvent.class); + + /** + * Resources to be opened automatically on next repaint. The list is + * automatically cleared when it has been sent to the client. + */ + private final LinkedList<OpenResource> openList = new LinkedList<OpenResource>(); + + /** + * A list of notifications that are waiting to be sent to the client. + * Cleared (set to null) when the notifications have been sent. + */ + private List<Notification> notifications; + + /** + * Event fired when uri fragment changes. + */ + public class FragmentChangedEvent extends EventObject { + + /** + * The new uri fragment + */ + private final String fragment; + + /** + * Creates a new instance of UriFragmentReader change event. + * + * @param source + * the Source of the event. + */ + public FragmentChangedEvent(Page source, String fragment) { + super(source); + this.fragment = fragment; + } + + /** + * Gets the root in which the fragment has changed. + * + * @return the root in which the fragment has changed + */ + public Page getPage() { + return (Page) getSource(); + } + + /** + * Get the new fragment + * + * @return the new fragment + */ + public String getFragment() { + return fragment; + } + } + + private EventRouter eventRouter; + + /** + * The current URI fragment. + */ + private String fragment; + + private final Root root; + + private int browserWindowWidth = -1; + private int browserWindowHeight = -1; + + private JavaScript javaScript; + + public Page(Root root) { + this.root = root; + } + + private void addListener(Class<?> eventType, Object target, Method method) { + if (eventRouter == null) { + eventRouter = new EventRouter(); + } + eventRouter.addListener(eventType, target, method); + } + + private void removeListener(Class<?> eventType, Object target, Method method) { + if (eventRouter != null) { + eventRouter.removeListener(eventType, target, method); + } + } + + public void addListener(Page.FragmentChangedListener listener) { + addListener(FragmentChangedEvent.class, listener, + FRAGMENT_CHANGED_METHOD); + } + + public void removeListener(Page.FragmentChangedListener listener) { + removeListener(FragmentChangedEvent.class, listener, + FRAGMENT_CHANGED_METHOD); + } + + /** + * Sets URI fragment. Optionally fires a {@link FragmentChangedEvent} + * + * @param newFragment + * id of the new fragment + * @param fireEvent + * true to fire event + * @see FragmentChangedEvent + * @see Page.FragmentChangedListener + */ + public void setFragment(String newFragment, boolean fireEvents) { + if (newFragment == null) { + throw new NullPointerException("The fragment may not be null"); + } + if (!newFragment.equals(fragment)) { + fragment = newFragment; + if (fireEvents) { + fireEvent(new FragmentChangedEvent(this, newFragment)); + } + root.requestRepaint(); + } + } + + private void fireEvent(EventObject event) { + if (eventRouter != null) { + eventRouter.fireEvent(event); + } + } + + /** + * Sets URI fragment. This method fires a {@link FragmentChangedEvent} + * + * @param newFragment + * id of the new fragment + * @see FragmentChangedEvent + * @see Page.FragmentChangedListener + */ + public void setFragment(String newFragment) { + setFragment(newFragment, true); + } + + /** + * Gets currently set URI fragment. + * <p> + * To listen changes in fragment, hook a + * {@link Page.FragmentChangedListener}. + * + * @return the current fragment in browser uri or null if not known + */ + public String getFragment() { + return fragment; + } + + public void init(WrappedRequest request) { + BrowserDetails browserDetails = request.getBrowserDetails(); + if (browserDetails != null) { + fragment = browserDetails.getUriFragment(); + } + } + + public WebBrowser getWebBrowser() { + return ((WebApplicationContext) root.getApplication().getContext()) + .getBrowser(); + } + + public void setBrowserWindowSize(Integer width, Integer height) { + boolean fireEvent = false; + + if (width != null) { + int newWidth = width.intValue(); + if (newWidth != browserWindowWidth) { + browserWindowWidth = newWidth; + fireEvent = true; + } + } + + if (height != null) { + int newHeight = height.intValue(); + if (newHeight != browserWindowHeight) { + browserWindowHeight = newHeight; + fireEvent = true; + } + } + + if (fireEvent) { + fireEvent(new BrowserWindowResizeEvent(this, browserWindowWidth, + browserWindowHeight)); + } + + } + + /** + * Adds a new {@link BrowserWindowResizeListener} to this root. The listener + * will be notified whenever the browser window within which this root + * resides is resized. + * + * @param resizeListener + * the listener to add + * + * @see BrowserWindowResizeListener#browserWindowResized(BrowserWindowResizeEvent) + * @see #setResizeLazy(boolean) + */ + public void addListener(BrowserWindowResizeListener resizeListener) { + addListener(BrowserWindowResizeEvent.class, resizeListener, + BROWSWER_RESIZE_METHOD); + } + + /** + * Removes a {@link BrowserWindowResizeListener} from this root. The + * listener will no longer be notified when the browser window is resized. + * + * @param resizeListener + * the listener to remove + */ + public void removeListener(BrowserWindowResizeListener resizeListener) { + removeListener(BrowserWindowResizeEvent.class, resizeListener, + BROWSWER_RESIZE_METHOD); + } + + /** + * Gets the last known height of the browser window in which this root + * resides. + * + * @return the browser window height in pixels + */ + public int getBrowserWindowHeight() { + return browserWindowHeight; + } + + /** + * Gets the last known width of the browser window in which this root + * resides. + * + * @return the browser window width in pixels + */ + public int getBrowserWindowWidth() { + return browserWindowWidth; + } + + public JavaScript getJavaScript() { + if (javaScript == null) { + // Create and attach on first use + javaScript = new JavaScript(); + javaScript.extend(root); + } + + return javaScript; + } + + public void paintContent(PaintTarget target) throws PaintException { + if (!openList.isEmpty()) { + for (final Iterator<OpenResource> i = openList.iterator(); i + .hasNext();) { + (i.next()).paintContent(target); + } + openList.clear(); + } + + // Paint notifications + if (notifications != null) { + target.startTag("notifications"); + for (final Iterator<Notification> it = notifications.iterator(); it + .hasNext();) { + final Notification n = it.next(); + target.startTag("notification"); + if (n.getCaption() != null) { + target.addAttribute( + VNotification.ATTRIBUTE_NOTIFICATION_CAPTION, + n.getCaption()); + } + if (n.getDescription() != null) { + target.addAttribute( + VNotification.ATTRIBUTE_NOTIFICATION_MESSAGE, + n.getDescription()); + } + if (n.getIcon() != null) { + target.addAttribute( + VNotification.ATTRIBUTE_NOTIFICATION_ICON, + n.getIcon()); + } + if (!n.isHtmlContentAllowed()) { + target.addAttribute( + VRoot.NOTIFICATION_HTML_CONTENT_NOT_ALLOWED, true); + } + target.addAttribute( + VNotification.ATTRIBUTE_NOTIFICATION_POSITION, + n.getPosition()); + target.addAttribute(VNotification.ATTRIBUTE_NOTIFICATION_DELAY, + n.getDelayMsec()); + if (n.getStyleName() != null) { + target.addAttribute( + VNotification.ATTRIBUTE_NOTIFICATION_STYLE, + n.getStyleName()); + } + target.endTag("notification"); + } + target.endTag("notifications"); + notifications = null; + } + + if (fragment != null) { + target.addAttribute(VRoot.FRAGMENT_VARIABLE, fragment); + } + + } + + /** + * Opens the given resource in this root. The contents of this Root is + * replaced by the {@code Resource}. + * + * @param resource + * the resource to show in this root + */ + public void open(Resource resource) { + openList.add(new OpenResource(resource, null, -1, -1, BORDER_DEFAULT)); + root.requestRepaint(); + } + + /** + * Opens the given resource in a window with the given name. + * <p> + * The supplied {@code windowName} is used as the target name in a + * window.open call in the client. This means that special values such as + * "_blank", "_self", "_top", "_parent" have special meaning. An empty or + * <code>null</code> window name is also a special case. + * </p> + * <p> + * "", null and "_self" as {@code windowName} all causes the resource to be + * opened in the current window, replacing any old contents. For + * downloadable content you should avoid "_self" as "_self" causes the + * client to skip rendering of any other changes as it considers them + * irrelevant (the page will be replaced by the resource). This can speed up + * the opening of a resource, but it might also put the client side into an + * inconsistent state if the window content is not completely replaced e.g., + * if the resource is downloaded instead of displayed in the browser. + * </p> + * <p> + * "_blank" as {@code windowName} causes the resource to always be opened in + * a new window or tab (depends on the browser and browser settings). + * </p> + * <p> + * "_top" and "_parent" as {@code windowName} works as specified by the HTML + * standard. + * </p> + * <p> + * Any other {@code windowName} will open the resource in a window with that + * name, either by opening a new window/tab in the browser or by replacing + * the contents of an existing window with that name. + * </p> + * + * @param resource + * the resource. + * @param windowName + * the name of the window. + */ + public void open(Resource resource, String windowName) { + openList.add(new OpenResource(resource, windowName, -1, -1, + BORDER_DEFAULT)); + root.requestRepaint(); + } + + /** + * Opens the given resource in a window with the given size, border and + * name. For more information on the meaning of {@code windowName}, see + * {@link #open(Resource, String)}. + * + * @param resource + * the resource. + * @param windowName + * the name of the window. + * @param width + * the width of the window in pixels + * @param height + * the height of the window in pixels + * @param border + * the border style of the window. See {@link #BORDER_NONE + * Window.BORDER_* constants} + */ + public void open(Resource resource, String windowName, int width, + int height, int border) { + openList.add(new OpenResource(resource, windowName, width, height, + border)); + root.requestRepaint(); + } + + /** + * Internal helper method to actually add a notification. + * + * @param notification + * the notification to add + */ + private void addNotification(Notification notification) { + if (notifications == null) { + notifications = new LinkedList<Notification>(); + } + notifications.add(notification); + root.requestRepaint(); + } + + /** + * Shows a notification message. + * + * @see Notification + * + * @param notification + * The notification message to show + * + * @deprecated Use Notification.show(Page) instead. + */ + @Deprecated + public void showNotification(Notification notification) { + addNotification(notification); + } + + /** + * Gets the Page to which the current root belongs. This is automatically + * defined when processing requests to the server. In other cases, (e.g. + * from background threads), the current root is not automatically defined. + * + * @see Root#getCurrent() + * + * @return the current page instance if available, otherwise + * <code>null</code> + */ + public static Page getCurrent() { + Root currentRoot = Root.getCurrent(); + if (currentRoot == null) { + return null; + } + return currentRoot.getPage(); + } + + /** + * Sets the page title. The page title is displayed by the browser e.g. as + * the title of the browser window or as the title of the tab. + * + * @param title + * the new page title to set + */ + public void setTitle(String title) { + root.getRpcProxy(PageClientRpc.class).setTitle(title); + } + +} diff --git a/src/com/vaadin/terminal/PaintTarget.java b/src/com/vaadin/terminal/PaintTarget.java index 9cfa324133..b658c9f4a3 100644 --- a/src/com/vaadin/terminal/PaintTarget.java +++ b/src/com/vaadin/terminal/PaintTarget.java @@ -9,7 +9,9 @@ import java.util.Map; import com.vaadin.terminal.StreamVariable.StreamingStartEvent; import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.Paintable; import com.vaadin.terminal.gwt.server.ClientConnector; +import com.vaadin.ui.Component; /** * This interface defines the methods for painting XML to the UIDL stream. @@ -39,7 +41,7 @@ public interface PaintTarget extends Serializable { /** * Result of starting to paint a Paintable ( - * {@link PaintTarget#startPaintable(ClientConnector, String)}). + * {@link PaintTarget#startPaintable(Component, String)}). * * @since 7.0 */ @@ -55,7 +57,7 @@ public interface PaintTarget extends Serializable { * changes. */ CACHED - }; + } /** * Prints element start tag of a paintable section. Starts a paintable @@ -73,8 +75,8 @@ public interface PaintTarget extends Serializable { * </p> * <p> * Each paintable being painted should be closed by a matching - * {@link #endPaintable(ClientConnector)} regardless of the - * {@link PaintStatus} returned. + * {@link #endPaintable(Component)} regardless of the {@link PaintStatus} + * returned. * </p> * * @param paintable @@ -89,15 +91,15 @@ public interface PaintTarget extends Serializable { * @see #startTag(String) * @since 7.0 (previously using startTag(Paintable, String)) */ - public PaintStatus startPaintable(ClientConnector paintable, String tag) + public PaintStatus startPaintable(Component paintable, String tag) throws PaintException; /** * Prints paintable element end tag. * - * Calls to {@link #startPaintable(ClientConnector, String)}should be - * matched by {@link #endPaintable(ClientConnector)}. If the parent tag is - * closed before every child tag is closed a PaintException is raised. + * Calls to {@link #startPaintable(Component, String)}should be matched by + * {@link #endPaintable(Component)}. If the parent tag is closed before + * every child tag is closed a PaintException is raised. * * @param paintable * the paintable to close. @@ -105,7 +107,7 @@ public interface PaintTarget extends Serializable { * if the paint operation failed. * @since 7.0 (previously using engTag(String)) */ - public void endPaintable(ClientConnector paintable) throws PaintException; + public void endPaintable(Component paintable) throws PaintException; /** * Prints element start tag. @@ -289,7 +291,7 @@ public interface PaintTarget extends Serializable { * the Paintable to be referenced on client side * @throws PaintException */ - public void addAttribute(String name, ClientConnector value) + public void addAttribute(String name, Component value) throws PaintException; /** @@ -421,8 +423,8 @@ public interface PaintTarget extends Serializable { * @throws PaintException * if the paint oparation fails */ - public void addVariable(VariableOwner owner, String name, - ClientConnector value) throws PaintException; + public void addVariable(VariableOwner owner, String name, Component value) + throws PaintException; /** * Adds a upload stream type variable. diff --git a/src/com/vaadin/terminal/UserError.java b/src/com/vaadin/terminal/UserError.java index baaf331fa0..a7a4fd89e2 100644 --- a/src/com/vaadin/terminal/UserError.java +++ b/src/com/vaadin/terminal/UserError.java @@ -44,6 +44,16 @@ public class UserError extends AbstractErrorMessage { super(textErrorMessage); } + /** + * Creates an 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, ContentMode contentMode, ErrorLevel errorLevel) { super(message); diff --git a/src/com/vaadin/terminal/gwt/DefaultWidgetSet.gwt.xml b/src/com/vaadin/terminal/gwt/DefaultWidgetSet.gwt.xml index 3487a655e5..77e3127745 100644 --- a/src/com/vaadin/terminal/gwt/DefaultWidgetSet.gwt.xml +++ b/src/com/vaadin/terminal/gwt/DefaultWidgetSet.gwt.xml @@ -71,9 +71,9 @@ <!-- Generate client side RPC manager for server to client RPC --> <generate-with - class="com.vaadin.terminal.gwt.widgetsetutils.RpcManagerGenerator"> + class="com.vaadin.terminal.gwt.widgetsetutils.GeneratedRpcMethodProviderGenerator"> <when-type-assignable - class="com.vaadin.terminal.gwt.client.communication.RpcManager" /> + class="com.vaadin.terminal.gwt.client.communication.GeneratedRpcMethodProvider" /> </generate-with> <generate-with diff --git a/src/com/vaadin/terminal/gwt/client/ApplicationConfiguration.java b/src/com/vaadin/terminal/gwt/client/ApplicationConfiguration.java index 8eeccb828d..960b0a8b0e 100644 --- a/src/com/vaadin/terminal/gwt/client/ApplicationConfiguration.java +++ b/src/com/vaadin/terminal/gwt/client/ApplicationConfiguration.java @@ -18,6 +18,7 @@ import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.user.client.Command; import com.google.gwt.user.client.Timer; +import com.google.gwt.user.client.Window; import com.vaadin.terminal.gwt.client.ui.UnknownComponentConnector; public class ApplicationConfiguration implements EntryPoint { @@ -209,7 +210,7 @@ public class ApplicationConfiguration implements EntryPoint { private HashMap<Integer, String> unknownComponents; - private Class<? extends ComponentConnector>[] classes = new Class[1024]; + private Class<? extends ServerConnector>[] classes = new Class[1024]; private boolean browserDetailsSent = false; private boolean widgetsetVersionSent = false; @@ -346,6 +347,7 @@ public class ApplicationConfiguration implements EntryPoint { */ public static void startApplication(final String applicationId) { Scheduler.get().scheduleDeferred(new ScheduledCommand() { + public void execute() { ApplicationConfiguration appConf = getConfigFromDOM(applicationId); ApplicationConnection a = GWT @@ -393,7 +395,7 @@ public class ApplicationConfiguration implements EntryPoint { return useDebugIdInDom; } - public Class<? extends ComponentConnector> getWidgetClassByEncodedTag( + public Class<? extends ServerConnector> getConnectorClassByEncodedTag( int tag) { try { return classes[tag]; @@ -508,7 +510,7 @@ public class ApplicationConfiguration implements EntryPoint { public void run() { pending = false; if (!isBusy()) { - Class<? extends ComponentConnector> nextType = getNextType(); + Class<? extends ServerConnector> nextType = getNextType(); if (nextType == null) { // ensured that all widgets are loaded deferredWidgetLoader = null; @@ -521,13 +523,13 @@ public class ApplicationConfiguration implements EntryPoint { } } - private Class<? extends ComponentConnector> getNextType() { - Class<? extends ComponentConnector>[] deferredLoadedWidgets = widgetSet - .getDeferredLoadedWidgets(); - if (deferredLoadedWidgets.length <= nextWidgetIndex) { + private Class<? extends ServerConnector> getNextType() { + Class<? extends ServerConnector>[] deferredLoadedConnectors = widgetSet + .getDeferredLoadedConnectors(); + if (deferredLoadedConnectors.length <= nextWidgetIndex) { return null; } else { - return deferredLoadedWidgets[nextWidgetIndex++]; + return deferredLoadedConnectors[nextWidgetIndex++]; } } @@ -568,6 +570,7 @@ public class ApplicationConfiguration implements EntryPoint { * GWT hosted mode. */ GWT.setUncaughtExceptionHandler(new UncaughtExceptionHandler() { + public void onUncaughtException(Throwable e) { /* * Note in case of null console (without ?debug) we eat @@ -602,12 +605,15 @@ public class ApplicationConfiguration implements EntryPoint { * * @return true if client side is currently been debugged */ - public native static boolean isDebugMode() + public static boolean isDebugMode() { + return isDebugAvailable() + && Window.Location.getParameter("debug") != null; + } + + private native static boolean isDebugAvailable() /*-{ if($wnd.vaadin.debug) { - var parameters = $wnd.location.search; - var re = /debug[^\/]*$/; - return re.test(parameters); + return true; } else { return false; } @@ -618,12 +624,11 @@ public class ApplicationConfiguration implements EntryPoint { * * @return <code>true</code> if debug logging should be quiet */ - public native static boolean isQuietDebugMode() - /*-{ - var uri = $wnd.location; - var re = /debug=q[^\/]*$/; - return re.test(uri); - }-*/; + public static boolean isQuietDebugMode() { + String debugParameter = Window.Location.getParameter("debug"); + return isDebugAvailable() && debugParameter != null + && debugParameter.startsWith("q"); + } /** * Checks whether information from the web browser (e.g. uri fragment and diff --git a/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java b/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java index 51a0ec3f02..f0470c8ee8 100644 --- a/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java +++ b/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java @@ -28,6 +28,7 @@ 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.JSONString; import com.google.gwt.regexp.shared.MatchResult; import com.google.gwt.regexp.shared.RegExp; @@ -39,12 +40,17 @@ import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.ui.HasWidgets; import com.google.gwt.user.client.ui.Widget; import com.vaadin.terminal.gwt.client.ApplicationConfiguration.ErrorMessage; +import com.vaadin.terminal.gwt.client.communication.HasJavaScriptConnectorHelper; import com.vaadin.terminal.gwt.client.communication.JsonDecoder; import com.vaadin.terminal.gwt.client.communication.JsonEncoder; import com.vaadin.terminal.gwt.client.communication.MethodInvocation; import com.vaadin.terminal.gwt.client.communication.RpcManager; import com.vaadin.terminal.gwt.client.communication.SerializerMap; +import com.vaadin.terminal.gwt.client.communication.SharedState; import com.vaadin.terminal.gwt.client.communication.StateChangeEvent; +import com.vaadin.terminal.gwt.client.communication.Type; +import com.vaadin.terminal.gwt.client.communication.UidlValue; +import com.vaadin.terminal.gwt.client.extensions.AbstractExtensionConnector; import com.vaadin.terminal.gwt.client.ui.AbstractComponentConnector; import com.vaadin.terminal.gwt.client.ui.VContextMenu; import com.vaadin.terminal.gwt.client.ui.dd.VDragAndDropManager; @@ -365,6 +371,25 @@ public class ApplicationConnection { }-*/; /** + * If on Liferay and logged in, ask the client side session management + * JavaScript to extend the session duration. + * + * Otherwise, Liferay client side JavaScript will explicitly expire the + * session even though the server side considers the session to be active. + * See ticket #8305 for more information. + */ + protected native void extendLiferaySession() + /*-{ + if ($wnd.Liferay && $wnd.Liferay.Session) { + $wnd.Liferay.Session.extend(); + // if the extend banner is visible, hide it + if ($wnd.Liferay.Session.banner) { + $wnd.Liferay.Session.banner.remove(); + } + } + }-*/; + + /** * Get the active Console for writing debug messages. May return an actual * logging console, or the NullConsole if debugging is not turned on. * @@ -846,6 +871,14 @@ public class ApplicationConnection { public void execute() { if (!hasActiveRequest()) { hideLoadingIndicator(); + + // If on Liferay and session expiration management is in + // use, extend session duration on each request. + // Doing it here rather than before the request to improve + // responsiveness. + // Postponed until the end of the next request if other + // requests still pending. + extendLiferaySession(); } } }); @@ -1271,27 +1304,24 @@ public class ApplicationConnection { List<ServerConnector> currentConnectors = new ArrayList<ServerConnector>( connectorMap.getConnectors()); for (ServerConnector c : currentConnectors) { - if (c instanceof ComponentConnector) { - ComponentConnector cc = (ComponentConnector) c; - if (cc.getParent() != null) { - if (!cc.getParent().getChildren().contains(cc)) { - VConsole.error("ERROR: Connector is connected to a parent but the parent does not contain the connector"); - } - } else if ((cc instanceof RootConnector && cc == getRootConnector())) { - // RootConnector for this connection, leave as-is - } else if (cc instanceof WindowConnector - && getRootConnector().hasSubWindow( - (WindowConnector) cc)) { - // Sub window attached to this RootConnector, leave - // as-is - } else { - // The connector has been detached from the - // hierarchy, unregister it and any possible - // children. The RootConnector should never be - // unregistered even though it has no parent. - connectorMap.unregisterConnector(cc); - unregistered++; + if (c.getParent() != null) { + if (!c.getParent().getChildren().contains(c)) { + VConsole.error("ERROR: Connector is connected to a parent but the parent does not contain the connector"); } + } else if ((c instanceof RootConnector && c == getRootConnector())) { + // RootConnector for this connection, leave as-is + } else if (c instanceof WindowConnector + && getRootConnector().hasSubWindow( + (WindowConnector) c)) { + // Sub window attached to this RootConnector, leave + // as-is + } else { + // The connector has been detached from the + // hierarchy, unregister it and any possible + // children. The RootConnector should never be + // unregistered even though it has no parent. + connectorMap.unregisterConnector(c); + unregistered++; } } @@ -1319,8 +1349,8 @@ public class ApplicationConnection { continue; } - Class<? extends ComponentConnector> connectorClass = configuration - .getWidgetClassByEncodedTag(connectorType); + Class<? extends ServerConnector> connectorClass = configuration + .getConnectorClassByEncodedTag(connectorType); // Connector does not exist so we must create it if (connectorClass != RootConnector.class) { @@ -1410,11 +1440,19 @@ public class ApplicationConnection { .getConnector(connectorId); if (null != connector) { - JSONArray stateDataAndType = new JSONArray( + JSONObject stateJson = new JSONObject( states.getJavaScriptObject(connectorId)); - JsonDecoder.decodeValue(stateDataAndType, - connector.getState(), connectorMap, + if (connector instanceof HasJavaScriptConnectorHelper) { + ((HasJavaScriptConnectorHelper) connector) + .getJavascriptConnectorHelper() + .setNativeState( + stateJson.getJavaScriptObject()); + } + + SharedState state = connector.getState(); + JsonDecoder.decodeValue(new Type(state.getClass() + .getName(), null), stateJson, state, ApplicationConnection.this); StateChangeEvent event = GWT @@ -1454,47 +1492,48 @@ public class ApplicationConnection { for (int i = 0; i < hierarchyKeys.length(); i++) { try { String connectorId = hierarchyKeys.get(i); - ServerConnector connector = connectorMap + ServerConnector parentConnector = connectorMap .getConnector(connectorId); - if (!(connector instanceof ComponentContainerConnector)) { - VConsole.error("Retrieved a hierarchy update for a connector (" - + connectorId - + ") that is not a ComponentContainerConnector"); - continue; - } - ComponentContainerConnector ccc = (ComponentContainerConnector) connector; - JsArrayString childConnectorIds = hierarchies .getJSStringArray(connectorId); int childConnectorSize = childConnectorIds.length(); List<ServerConnector> newChildren = new ArrayList<ServerConnector>(); + List<ComponentConnector> newComponents = new ArrayList<ComponentConnector>(); for (int connectorIndex = 0; connectorIndex < childConnectorSize; connectorIndex++) { String childConnectorId = childConnectorIds .get(connectorIndex); - ComponentConnector childConnector = (ComponentConnector) connectorMap + ServerConnector childConnector = connectorMap .getConnector(childConnectorId); if (childConnector == null) { VConsole.error("Hierarchy claims that " + childConnectorId + " is a child for " + connectorId + " (" - + connector.getClass().getName() + + parentConnector.getClass().getName() + ") but no connector with id " + childConnectorId + " has been registered"); continue; } newChildren.add(childConnector); - if (childConnector.getParent() != ccc) { + if (childConnector instanceof ComponentConnector) { + newComponents + .add((ComponentConnector) childConnector); + } else if (!(childConnector instanceof AbstractExtensionConnector)) { + throw new IllegalStateException( + Util.getConnectorString(childConnector) + + " is not a ComponentConnector nor an AbstractExtensionConnector"); + } + if (childConnector.getParent() != parentConnector) { // Avoid extra calls to setParent - childConnector.setParent(ccc); + childConnector.setParent(parentConnector); } } // TODO This check should be done on the server side in // the future so the hierarchy update is only sent when // something actually has changed - List<ComponentConnector> oldChildren = ccc + List<ServerConnector> oldChildren = parentConnector .getChildren(); boolean actuallyChanged = !Util.collectionsEquals( oldChildren, newChildren); @@ -1503,19 +1542,34 @@ public class ApplicationConnection { continue; } - // Fire change event if the hierarchy has changed - ConnectorHierarchyChangeEvent event = GWT - .create(ConnectorHierarchyChangeEvent.class); - event.setOldChildren(oldChildren); - event.setConnector(ccc); - ccc.setChildren((List) newChildren); - events.add(event); + if (parentConnector instanceof ComponentContainerConnector) { + ComponentContainerConnector ccc = (ComponentContainerConnector) parentConnector; + List<ComponentConnector> oldComponents = ccc + .getChildComponents(); + if (!Util.collectionsEquals(oldComponents, + newComponents)) { + // Fire change event if the hierarchy has + // changed + ConnectorHierarchyChangeEvent event = GWT + .create(ConnectorHierarchyChangeEvent.class); + event.setOldChildren(oldComponents); + event.setConnector(parentConnector); + ccc.setChildComponents(newComponents); + events.add(event); + } + } else if (!newComponents.isEmpty()) { + VConsole.error("Hierachy claims " + + Util.getConnectorString(parentConnector) + + " has component children even though it isn't a ComponentContainerConnector"); + } + + parentConnector.setChildren(newChildren); // Remove parent for children that are no longer // attached to this (avoid updating children if they // have already been assigned to a new parent) - for (ComponentConnector oldChild : oldChildren) { - if (oldChild.getParent() != ccc) { + for (ServerConnector oldChild : oldChildren) { + if (oldChild.getParent() != parentConnector) { continue; } @@ -1543,11 +1597,8 @@ public class ApplicationConnection { for (int i = 0; i < rpcLength; i++) { try { JSONArray rpcCall = (JSONArray) rpcCalls.get(i); - MethodInvocation invocation = parseMethodInvocation(rpcCall); - VConsole.log("Server to client RPC call: " - + invocation); - rpcManager.applyInvocation(invocation, - getConnectorMap()); + rpcManager.parseAndApplyInvocation(rpcCall, + ApplicationConnection.this); } catch (final Throwable e) { VConsole.error(e); } @@ -1560,21 +1611,6 @@ public class ApplicationConnection { ApplicationConfiguration.runWhenWidgetsLoaded(c); } - private MethodInvocation parseMethodInvocation(JSONArray rpcCall) { - String connectorId = ((JSONString) rpcCall.get(0)).stringValue(); - String interfaceName = ((JSONString) rpcCall.get(1)).stringValue(); - String methodName = ((JSONString) rpcCall.get(2)).stringValue(); - JSONArray parametersJson = (JSONArray) rpcCall.get(3); - Object[] parameters = new Object[parametersJson.size()]; - for (int j = 0; j < parametersJson.size(); ++j) { - parameters[j] = JsonDecoder.decodeValue( - (JSONArray) parametersJson.get(j), null, getConnectorMap(), - this); - } - return new MethodInvocation(connectorId, interfaceName, methodName, - parameters); - } - // Redirect browser, null reloads current page private static native void redirect(String url) /*-{ @@ -1591,7 +1627,7 @@ public class ApplicationConnection { // TODO could eliminate invocations of same shared variable setter addMethodInvocationToQueue(new MethodInvocation(connectorId, UPDATE_VARIABLE_INTERFACE, UPDATE_VARIABLE_METHOD, - new Object[] { variableName, value }), immediate); + new Object[] { variableName, new UidlValue(value) }), immediate); } /** @@ -1692,7 +1728,7 @@ public class ApplicationConnection { // TODO non-static encoder? type registration? paramJson.set(i, JsonEncoder.encode( invocation.getParameters()[i], - restrictToInternalTypes, getConnectorMap(), this)); + restrictToInternalTypes, this)); } invocationJson.set(3, paramJson); reqJson.set(reqJson.size(), invocationJson); @@ -2079,7 +2115,9 @@ public class ApplicationConnection { @Deprecated public ComponentConnector getPaintable(UIDL uidl) { - return getConnector(uidl.getId(), Integer.parseInt(uidl.getTag())); + // Non-component connectors shouldn't be painted from legacy connectors + return (ComponentConnector) getConnector(uidl.getId(), + Integer.parseInt(uidl.getTag())); } /** @@ -2098,17 +2136,17 @@ public class ApplicationConnection { * @return Either an existing ComponentConnector or a new ComponentConnector * of the given type */ - public ComponentConnector getConnector(String connectorId, int connectorType) { + public ServerConnector getConnector(String connectorId, int connectorType) { if (!connectorMap.hasConnector(connectorId)) { return createAndRegisterConnector(connectorId, connectorType); } - return (ComponentConnector) connectorMap.getConnector(connectorId); + return connectorMap.getConnector(connectorId); } /** - * Creates a new ComponentConnector with the given type and id. + * Creates a new ServerConnector with the given type and id. * - * Creates and registers a new ComponentConnector of the given type. Should + * Creates and registers a new ServerConnector of the given type. Should * never be called with the connector id of an existing connector. * * @param connectorId @@ -2116,12 +2154,12 @@ public class ApplicationConnection { * @param connectorType * Type of the connector, as passed from the server side * - * @return A new ComponentConnector of the given type + * @return A new ServerConnector of the given type */ - private ComponentConnector createAndRegisterConnector(String connectorId, + private ServerConnector createAndRegisterConnector(String connectorId, int connectorType) { // Create and register a new connector with the given type - ComponentConnector p = widgetSet.createWidget(connectorType, + ServerConnector p = widgetSet.createConnector(connectorType, configuration); connectorMap.registerConnector(connectorId, p); p.doInit(connectorId, this); diff --git a/src/com/vaadin/terminal/gwt/client/BrowserInfo.java b/src/com/vaadin/terminal/gwt/client/BrowserInfo.java index e12c002930..82cf925ec1 100644 --- a/src/com/vaadin/terminal/gwt/client/BrowserInfo.java +++ b/src/com/vaadin/terminal/gwt/client/BrowserInfo.java @@ -31,6 +31,9 @@ public class BrowserInfo { private static final String OS_ANDROID = "android"; private static final String OS_IOS = "ios"; + // Common CSS class for all touch devices + private static final String UI_TOUCH = "touch"; + private static BrowserInfo instance; private static String cssClass = null; @@ -169,7 +172,9 @@ public class BrowserInfo { if (osClass != null) { cssClass = cssClass + " " + prefix + osClass; } - + if (isTouchDevice()) { + cssClass = cssClass + " " + prefix + UI_TOUCH; + } } return cssClass; @@ -344,15 +349,14 @@ public class BrowserInfo { if (!isTouchDevice()) { return false; } - - if (isAndroid() && isWebkit() && getWebkitVersion() < 534) { - return true; + if (isAndroid() && isWebkit() && getWebkitVersion() >= 534) { + return false; } - // if (isIOS() && isWebkit() && getWebkitVersion() < ???) { - // return true; + // Cannot enable native touch scrolling on iOS 5 until #8792 is resolved + // if (isIOS() && isWebkit() && getWebkitVersion() >= 534) { + // return false; // } - - return false; + return true; } /** diff --git a/src/com/vaadin/terminal/gwt/client/ComponentConnector.java b/src/com/vaadin/terminal/gwt/client/ComponentConnector.java index 5f9171084e..4e6a690a3c 100644 --- a/src/com/vaadin/terminal/gwt/client/ComponentConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ComponentConnector.java @@ -71,29 +71,6 @@ public interface ComponentConnector extends ServerConnector { public boolean isRelativeHeight(); /** - * Returns the parent of this connector. Can be null for only the root - * connector. - * - * @return The parent of this connector, as set by - * {@link #setParent(ComponentContainerConnector)}. - */ - public ComponentContainerConnector getParent(); - - /** - * Sets the parent for this connector. This method should only be called by - * the framework to ensure that the connector hierarchy on the client side - * and the server side are in sync. - * <p> - * Note that calling this method does not fire a - * {@link ConnectorHierarchyChangeEvent}. The event is fired only when the - * whole hierarchy has been updated. - * - * @param parent - * The new parent of the connector - */ - public void setParent(ComponentContainerConnector parent); - - /** * Checks if the connector is read only. * * @deprecated This belongs in AbstractFieldConnector, see #8514 diff --git a/src/com/vaadin/terminal/gwt/client/ComponentContainerConnector.java b/src/com/vaadin/terminal/gwt/client/ComponentContainerConnector.java index 05334e8049..08ce3d31dc 100644 --- a/src/com/vaadin/terminal/gwt/client/ComponentContainerConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ComponentContainerConnector.java @@ -14,7 +14,7 @@ import com.vaadin.terminal.gwt.client.ConnectorHierarchyChangeEvent.ConnectorHie * An interface used by client-side connectors whose widget is a component * container (implements {@link HasWidgets}). */ -public interface ComponentContainerConnector extends ComponentConnector { +public interface ComponentContainerConnector extends ServerConnector { /** * Update child components caption, description and error message. @@ -42,7 +42,7 @@ public interface ComponentContainerConnector extends ComponentConnector { * @return A collection of children for this connector. An empty collection * if there are no children. Never returns null. */ - public List<ComponentConnector> getChildren(); + public List<ComponentConnector> getChildComponents(); /** * Sets the children for this connector. This method should only be called @@ -50,14 +50,14 @@ public interface ComponentContainerConnector extends ComponentConnector { * side and the server side are in sync. * <p> * Note that calling this method does not call - * {@link #connectorHierarchyChanged(ConnectorHierarchyChangeEvent)}. The - * event method is called only when the hierarchy has been updated for all - * connectors. + * {@link ConnectorHierarchyChangeHandler#onConnectorHierarchyChange(ConnectorHierarchyChangeEvent)} + * . The event method is called only when the hierarchy has been updated for + * all connectors. * * @param children * The new child connectors */ - public void setChildren(List<ComponentConnector> children); + public void setChildComponents(List<ComponentConnector> children); /** * Adds a handler that is called whenever the child hierarchy of this diff --git a/src/com/vaadin/terminal/gwt/client/ComponentLocator.java b/src/com/vaadin/terminal/gwt/client/ComponentLocator.java index a4df6c6cc4..0e7a0c1d1c 100644 --- a/src/com/vaadin/terminal/gwt/client/ComponentLocator.java +++ b/src/com/vaadin/terminal/gwt/client/ComponentLocator.java @@ -12,6 +12,7 @@ import com.google.gwt.user.client.Element; import com.google.gwt.user.client.ui.HasWidgets; import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.communication.SharedState; import com.vaadin.terminal.gwt.client.ui.SubPartAware; import com.vaadin.terminal.gwt.client.ui.VBoxLayout; import com.vaadin.terminal.gwt.client.ui.gridlayout.VGridLayout; @@ -441,8 +442,8 @@ public class ComponentLocator { } else if (w == null) { String id = part; // Must be old static pid (PID_S*) - ComponentConnector connector = (ComponentConnector) ConnectorMap - .get(client).getConnector(id); + ServerConnector connector = ConnectorMap.get(client) + .getConnector(id); if (connector == null) { // Lookup by debugId // TODO Optimize this @@ -450,8 +451,8 @@ public class ComponentLocator { id.substring(5)); } - if (connector != null) { - w = connector.getWidget(); + if (connector instanceof ComponentConnector) { + w = ((ComponentConnector) connector).getWidget(); } else { // Not found return null; @@ -597,19 +598,16 @@ public class ComponentLocator { return w; } - private ComponentConnector findConnectorById(ComponentConnector root, - String id) { - if (root instanceof ComponentConnector - && id.equals(root.getState().getDebugId())) { + private ServerConnector findConnectorById(ServerConnector root, String id) { + SharedState state = root.getState(); + if (state instanceof ComponentState + && id.equals(((ComponentState) state).getDebugId())) { return root; } - if (root instanceof ComponentContainerConnector) { - ComponentContainerConnector ccc = (ComponentContainerConnector) root; - for (ComponentConnector child : ccc.getChildren()) { - ComponentConnector found = findConnectorById(child, id); - if (found != null) { - return found; - } + for (ServerConnector child : root.getChildren()) { + ServerConnector found = findConnectorById(child, id); + if (found != null) { + return found; } } diff --git a/src/com/vaadin/terminal/gwt/client/ComponentState.java b/src/com/vaadin/terminal/gwt/client/ComponentState.java index 7f01300d68..a603368f44 100644 --- a/src/com/vaadin/terminal/gwt/client/ComponentState.java +++ b/src/com/vaadin/terminal/gwt/client/ComponentState.java @@ -24,7 +24,6 @@ public class ComponentState extends SharedState { private String width = ""; private boolean readOnly = false; private boolean immediate = false; - private boolean enabled = true; private String description = ""; // Note: for the caption, there is a difference between null and an empty // string! @@ -195,29 +194,6 @@ public class ComponentState extends SharedState { } /** - * Returns true if the component is enabled. - * - * @see com.vaadin.ui.Component#isEnabled() - * - * @return true if the component is enabled - */ - public boolean isEnabled() { - return enabled; - } - - /** - * Enables or disables the component. - * - * @see com.vaadin.ui.Component#setEnabled(boolean) - * - * @param enabled - * new mode for the component - */ - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - /** * Gets the description of the component (typically shown as tooltip). * * @see com.vaadin.ui.AbstractComponent#getDescription() diff --git a/src/com/vaadin/terminal/gwt/client/Connector.java b/src/com/vaadin/terminal/gwt/client/Connector.java index 1c61052735..9b2fcf61f1 100644 --- a/src/com/vaadin/terminal/gwt/client/Connector.java +++ b/src/com/vaadin/terminal/gwt/client/Connector.java @@ -46,4 +46,12 @@ public interface Connector extends Serializable { */ public String getConnectorId(); + /** + * Gets the parent connector of this connector, or <code>null</code> if the + * connector is not attached to any parent. + * + * @return the parent connector, or <code>null</code> if there is no parent. + */ + public Connector getParent(); + } diff --git a/src/com/vaadin/terminal/gwt/client/ConnectorMap.java b/src/com/vaadin/terminal/gwt/client/ConnectorMap.java index e57776cf1c..efb50b5e00 100644 --- a/src/com/vaadin/terminal/gwt/client/ConnectorMap.java +++ b/src/com/vaadin/terminal/gwt/client/ConnectorMap.java @@ -159,17 +159,16 @@ public class ConnectorMap { idToConnector.remove(connectorId); connector.onUnregister(); - if (connector instanceof ComponentContainerConnector) { - for (ComponentConnector child : ((ComponentContainerConnector) connector) - .getChildren()) { - if (child.getParent() == connector) { - // Only unregister children that are actually connected to - // this parent. For instance when moving connectors from one - // layout to another and removing the first layout it will - // still contain references to its old children, which are - // now attached to another connector. - unregisterConnector(child); - } + for (ServerConnector child : connector.getChildren()) { + if (child.getParent() == connector) { + /* + * Only unregister children that are actually connected to this + * parent. For instance when moving connectors from one layout + * to another and removing the first layout it will still + * contain references to its old children, which are now + * attached to another connector. + */ + unregisterConnector(child); } } } @@ -209,12 +208,20 @@ public class ConnectorMap { */ @Deprecated public TooltipInfo getTooltipInfo(ComponentConnector paintable, Object key) { - return getComponentDetail(paintable).getTooltipInfo(key); + ComponentDetail componentDetail = getComponentDetail(paintable); + if (componentDetail == null) { + return null; + } + return componentDetail.getTooltipInfo(key); } @Deprecated public TooltipInfo getWidgetTooltipInfo(Widget widget, Object key) { - return getTooltipInfo(getConnector(widget), key); + ComponentConnector connector = getConnector(widget); + if (connector == null) { + return null; + } + return getTooltipInfo(connector, key); } public Collection<? extends ServerConnector> getConnectors() { diff --git a/src/com/vaadin/terminal/gwt/client/JavaScriptConnectorHelper.java b/src/com/vaadin/terminal/gwt/client/JavaScriptConnectorHelper.java new file mode 100644 index 0000000000..bd62a759cb --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/JavaScriptConnectorHelper.java @@ -0,0 +1,372 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.core.client.JsArray; +import com.google.gwt.json.client.JSONArray; +import com.google.gwt.user.client.Element; +import com.vaadin.terminal.gwt.client.communication.MethodInvocation; +import com.vaadin.terminal.gwt.client.communication.StateChangeEvent; +import com.vaadin.terminal.gwt.client.communication.StateChangeEvent.StateChangeHandler; + +public class JavaScriptConnectorHelper { + + public interface JavaScriptConnectorState { + public Set<String> getCallbackNames(); + + public Map<String, Set<String>> getRpcInterfaces(); + } + + private final ServerConnector connector; + private final JavaScriptObject nativeState = JavaScriptObject + .createObject(); + private final JavaScriptObject rpcMap = JavaScriptObject.createObject(); + + private final Map<String, JavaScriptObject> rpcObjects = new HashMap<String, JavaScriptObject>(); + private final Map<String, Set<String>> rpcMethods = new HashMap<String, Set<String>>(); + + private JavaScriptObject connectorWrapper; + private int tag; + + private boolean inited = false; + + public JavaScriptConnectorHelper(ServerConnector connector) { + this.connector = connector; + + // Wildcard rpc object + rpcObjects.put("", JavaScriptObject.createObject()); + } + + public void init() { + connector.addStateChangeHandler(new StateChangeHandler() { + public void onStateChanged(StateChangeEvent stateChangeEvent) { + JavaScriptObject wrapper = getConnectorWrapper(); + JavaScriptConnectorState state = getConnectorState(); + + for (String callback : state.getCallbackNames()) { + ensureCallback(JavaScriptConnectorHelper.this, wrapper, + callback); + } + + for (Entry<String, Set<String>> entry : state + .getRpcInterfaces().entrySet()) { + String rpcName = entry.getKey(); + String jsName = getJsInterfaceName(rpcName); + if (!rpcObjects.containsKey(jsName)) { + Set<String> methods = entry.getValue(); + rpcObjects.put(jsName, + createRpcObject(rpcName, methods)); + + // Init all methods for wildcard rpc + for (String method : methods) { + JavaScriptObject wildcardRpcObject = rpcObjects + .get(""); + Set<String> interfaces = rpcMethods.get(method); + if (interfaces == null) { + interfaces = new HashSet<String>(); + rpcMethods.put(method, interfaces); + attachRpcMethod(wildcardRpcObject, null, method); + } + interfaces.add(rpcName); + } + } + } + + // Init after setting up callbacks & rpc + if (!inited) { + initJavaScript(); + inited = true; + } + + fireNativeStateChange(wrapper); + } + }); + } + + private static String getJsInterfaceName(String rpcName) { + return rpcName.replace('$', '.'); + } + + protected JavaScriptObject createRpcObject(String iface, Set<String> methods) { + JavaScriptObject object = JavaScriptObject.createObject(); + + for (String method : methods) { + attachRpcMethod(object, iface, method); + } + + return object; + } + + private boolean initJavaScript() { + ApplicationConfiguration conf = connector.getConnection() + .getConfiguration(); + ArrayList<String> attemptedNames = new ArrayList<String>(); + Integer tag = Integer.valueOf(this.tag); + while (tag != null) { + String serverSideClassName = conf.getServerSideClassNameForTag(tag); + String initFunctionName = serverSideClassName + .replaceAll("\\.", "_"); + if (tryInitJs(initFunctionName, getConnectorWrapper())) { + VConsole.log("JavaScript connector initialized using " + + initFunctionName); + return true; + } else { + VConsole.log("No JavaScript function " + initFunctionName + + " found"); + attemptedNames.add(initFunctionName); + tag = conf.getParentTag(tag.intValue()); + } + } + VConsole.log("No JavaScript init for connector not found"); + showInitProblem(attemptedNames); + return false; + } + + protected void showInitProblem(ArrayList<String> attemptedNames) { + // Default does nothing + } + + private static native boolean tryInitJs(String initFunctionName, + JavaScriptObject connectorWrapper) + /*-{ + if (typeof $wnd[initFunctionName] == 'function') { + $wnd[initFunctionName].apply(connectorWrapper); + return true; + } else { + return false; + } + }-*/; + + private JavaScriptObject getConnectorWrapper() { + if (connectorWrapper == null) { + connectorWrapper = createConnectorWrapper(this, nativeState, + rpcMap, connector.getConnectorId(), rpcObjects); + } + + return connectorWrapper; + } + + private static native void fireNativeStateChange( + JavaScriptObject connectorWrapper) + /*-{ + if (typeof connectorWrapper.onStateChange == 'function') { + connectorWrapper.onStateChange(); + } + }-*/; + + private static native JavaScriptObject createConnectorWrapper( + JavaScriptConnectorHelper h, JavaScriptObject nativeState, + JavaScriptObject registeredRpc, String connectorId, + Map<String, JavaScriptObject> rpcObjects) + /*-{ + return { + 'getConnectorId': function() { + return connectorId; + }, + 'getParentId': $entry(function(connectorId) { + return h.@com.vaadin.terminal.gwt.client.JavaScriptConnectorHelper::getParentId(Ljava/lang/String;)(connectorId); + }), + 'getState': function() { + return nativeState; + }, + 'getRpcProxy': $entry(function(iface) { + if (!iface) { + iface = ''; + } + return rpcObjects.@java.util.Map::get(Ljava/lang/Object;)(iface); + }), + 'getWidgetElement': $entry(function(connectorId) { + return h.@com.vaadin.terminal.gwt.client.JavaScriptConnectorHelper::getWidgetElement(Ljava/lang/String;)(connectorId); + }), + 'registerRpc': function(iface, rpcHandler) { + //registerRpc(handler) -> registerRpc('', handler); + if (!rpcHandler) { + rpcHandler = iface; + iface = ''; + } + if (!registeredRpc[iface]) { + registeredRpc[iface] = []; + } + registeredRpc[iface].push(rpcHandler); + }, + }; + }-*/; + + private native void attachRpcMethod(JavaScriptObject rpc, String iface, + String method) + /*-{ + var self = this; + rpc[method] = $entry(function() { + self.@com.vaadin.terminal.gwt.client.JavaScriptConnectorHelper::fireRpc(Ljava/lang/String;Ljava/lang/String;Lcom/google/gwt/core/client/JsArray;)(iface, method, arguments); + }); + }-*/; + + private String getParentId(String connectorId) { + ServerConnector target = getConnector(connectorId); + if (target == null) { + return null; + } + ServerConnector parent = target.getParent(); + if (parent == null) { + return null; + } else { + return parent.getConnectorId(); + } + } + + private Element getWidgetElement(String connectorId) { + ServerConnector target = getConnector(connectorId); + if (target instanceof ComponentConnector) { + return ((ComponentConnector) target).getWidget().getElement(); + } else { + return null; + } + } + + private ServerConnector getConnector(String connectorId) { + if (connectorId == null || connectorId.length() == 0) { + return connector; + } + + return ConnectorMap.get(connector.getConnection()) + .getConnector(connectorId); + } + + private void fireRpc(String iface, String method, + JsArray<JavaScriptObject> arguments) { + if (iface == null) { + iface = findWildcardInterface(method); + } + + JSONArray argumentsArray = new JSONArray(arguments); + Object[] parameters = new Object[arguments.length()]; + for (int i = 0; i < parameters.length; i++) { + parameters[i] = argumentsArray.get(i); + } + connector.getConnection().addMethodInvocationToQueue( + new MethodInvocation(connector.getConnectorId(), iface, method, + parameters), true); + } + + private String findWildcardInterface(String method) { + Set<String> interfaces = rpcMethods.get(method); + if (interfaces.size() == 1) { + return interfaces.iterator().next(); + } else { + // TODO Resolve conflicts using argument count and types + String interfaceList = ""; + for (String iface : interfaces) { + if (interfaceList.length() != 0) { + interfaceList += ", "; + } + interfaceList += getJsInterfaceName(iface); + } + + throw new IllegalStateException( + "Can not call method " + + method + + " for wildcard rpc proxy because the function is defined for multiple rpc interfaces: " + + interfaceList + + ". Retrieve a rpc proxy for a specific interface using getRpcProxy(interfaceName) to use the function."); + } + } + + private void fireCallback(String name, JsArray<JavaScriptObject> arguments) { + MethodInvocation invocation = new MethodInvocation( + connector.getConnectorId(), + "com.vaadin.ui.JavaScript$JavaScriptCallbackRpc", "call", + new Object[] { name, new JSONArray(arguments) }); + connector.getConnection().addMethodInvocationToQueue(invocation, true); + } + + public void setNativeState(JavaScriptObject state) { + updateNativeState(nativeState, state); + } + + private static native void updateNativeState(JavaScriptObject state, + JavaScriptObject input) + /*-{ + // Copy all fields to existing state object + for(var key in state) { + if (state.hasOwnProperty(key)) { + delete state[key]; + } + } + + for(var key in input) { + if (input.hasOwnProperty(key)) { + state[key] = input[key]; + } + } + }-*/; + + public Object[] decodeRpcParameters(JSONArray parametersJson) { + return new Object[] { parametersJson.getJavaScriptObject() }; + } + + public void setTag(int tag) { + this.tag = tag; + } + + public void invokeJsRpc(MethodInvocation invocation, + JSONArray parametersJson) { + String iface = invocation.getInterfaceName(); + String method = invocation.getMethodName(); + if ("com.vaadin.ui.JavaScript$JavaScriptCallbackRpc".equals(iface) + && "call".equals(method)) { + String callbackName = parametersJson.get(0).isString() + .stringValue(); + JavaScriptObject arguments = parametersJson.get(1).isArray() + .getJavaScriptObject(); + invokeCallback(getConnectorWrapper(), callbackName, arguments); + } else { + JavaScriptObject arguments = parametersJson.getJavaScriptObject(); + invokeJsRpc(rpcMap, iface, method, arguments); + // Also invoke wildcard interface + invokeJsRpc(rpcMap, "", method, arguments); + } + } + + private static native void invokeCallback(JavaScriptObject connector, + String name, JavaScriptObject arguments) + /*-{ + connector[name].apply(connector, arguments); + }-*/; + + private static native void invokeJsRpc(JavaScriptObject rpcMap, + String interfaceName, String methodName, JavaScriptObject parameters) + /*-{ + var targets = rpcMap[interfaceName]; + if (!targets) { + return; + } + for(var i = 0; i < targets.length; i++) { + var target = targets[i]; + target[methodName].apply(target, parameters); + } + }-*/; + + private static native void ensureCallback(JavaScriptConnectorHelper h, + JavaScriptObject connector, String name) + /*-{ + connector[name] = $entry(function() { + var args = Array.prototype.slice.call(arguments, 0); + h.@com.vaadin.terminal.gwt.client.JavaScriptConnectorHelper::fireCallback(Ljava/lang/String;Lcom/google/gwt/core/client/JsArray;)(name, args); + }); + }-*/; + + private JavaScriptConnectorState getConnectorState() { + return (JavaScriptConnectorState) connector.getState(); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/JavaScriptExtension.java b/src/com/vaadin/terminal/gwt/client/JavaScriptExtension.java new file mode 100644 index 0000000000..2a97e4a770 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/JavaScriptExtension.java @@ -0,0 +1,32 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client; + +import com.vaadin.terminal.AbstractJavaScriptExtension; +import com.vaadin.terminal.gwt.client.communication.HasJavaScriptConnectorHelper; +import com.vaadin.terminal.gwt.client.extensions.AbstractExtensionConnector; +import com.vaadin.terminal.gwt.client.ui.Connect; + +@Connect(AbstractJavaScriptExtension.class) +public final class JavaScriptExtension extends AbstractExtensionConnector + implements HasJavaScriptConnectorHelper { + private final JavaScriptConnectorHelper helper = new JavaScriptConnectorHelper( + this); + + @Override + protected void init() { + super.init(); + helper.init(); + } + + public JavaScriptConnectorHelper getJavascriptConnectorHelper() { + return helper; + } + + @Override + public JavaScriptExtensionState getState() { + return (JavaScriptExtensionState) super.getState(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/JavaScriptExtensionState.java b/src/com/vaadin/terminal/gwt/client/JavaScriptExtensionState.java new file mode 100644 index 0000000000..e7bfbc4bb2 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/JavaScriptExtensionState.java @@ -0,0 +1,36 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import com.vaadin.terminal.gwt.client.JavaScriptConnectorHelper.JavaScriptConnectorState; +import com.vaadin.terminal.gwt.client.communication.SharedState; + +public class JavaScriptExtensionState extends SharedState implements + JavaScriptConnectorState { + + private Set<String> callbackNames = new HashSet<String>(); + private Map<String, Set<String>> rpcInterfaces = new HashMap<String, Set<String>>(); + + public Set<String> getCallbackNames() { + return callbackNames; + } + + public void setCallbackNames(Set<String> callbackNames) { + this.callbackNames = callbackNames; + } + + public Map<String, Set<String>> getRpcInterfaces() { + return rpcInterfaces; + } + + public void setRpcInterfaces(Map<String, Set<String>> rpcInterfaces) { + this.rpcInterfaces = rpcInterfaces; + } +} diff --git a/src/com/vaadin/terminal/gwt/client/LayoutManager.java b/src/com/vaadin/terminal/gwt/client/LayoutManager.java index 239a948a10..41ad0e9f47 100644 --- a/src/com/vaadin/terminal/gwt/client/LayoutManager.java +++ b/src/com/vaadin/terminal/gwt/client/LayoutManager.java @@ -120,7 +120,7 @@ public class LayoutManager { /** * Assigns a measured size to an element. Method defined as protected to - * allow separate implementation for IE8 in which delete not always works. + * allow separate implementation for IE8. * * @param element * the dom element to attach the measured size to @@ -139,18 +139,14 @@ public class LayoutManager { }-*/; /** - * Get the measured size of the given element. If no size is set, use the - * default size instead. - * - * Method defined as protected to allow separate implementation for IE8 - * (performance reason: storing any data in the DOM causes a reflow). + * Gets the measured size for an element. Method defined as protected to + * allow separate implementation for IE8. * * @param element - * the dom element whose measured size to get + * The element to get measured size for * @param defaultSize - * a fallback size if the element doesn't have a measured size - * stored - * @return + * The size to return if no measured size could be found + * @return The measured size for the element or {@literal defaultSize} */ protected native MeasuredSize getMeasuredSize(Element element, MeasuredSize defaultSize) @@ -185,7 +181,7 @@ public class LayoutManager { return; } measuredSize.removeDependent(owner.getConnectorId()); - stopMeasuringIfUnnecessary(element); + stopMeasuringIfUnecessary(element); } public boolean isLayoutRunning() { @@ -270,16 +266,10 @@ public class LayoutManager { VConsole.log(" Measured " + measuredConnectorCount + " elements in " + measureTime + " ms"); - VConsole.log(" Total of " + measureCount - + " measurement operations"); - if (!listenersToFire.isEmpty()) { for (Element element : listenersToFire) { Collection<ElementResizeListener> listeners = elementResizeListeners .get(element); - if (listeners == null) { - continue; - } ElementResizeListener[] array = listeners .toArray(new ElementResizeListener[listeners.size()]); ElementResizeEvent event = new ElementResizeEvent(this, @@ -409,8 +399,13 @@ public class LayoutManager { ((PostLayoutListener) connector).postLayout(); } } - VConsole.log("Invoke post layout listeners in " - + (totalDuration.elapsedMillis() - postLayoutStart) + " ms"); + int postLayoutDone = (totalDuration.elapsedMillis() - postLayoutStart); + VConsole.log("Invoke post layout listeners in " + postLayoutDone + + " ms"); + + cleanMeasuredSizes(); + int cleaningDone = (totalDuration.elapsedMillis() - postLayoutDone); + VConsole.log("Cleaned old measured sizes in " + cleaningDone + "ms"); VConsole.log("Total layout phase time: " + totalDuration.elapsedMillis() + "ms"); @@ -436,11 +431,13 @@ public class LayoutManager { for (ComponentConnector componentConnector : pendingOverflowFixes) { // Delay the overflow fix if the involved connectors might still // change - if (!currentDependencyTree - .noMoreChangesExpected(componentConnector) - || !currentDependencyTree - .noMoreChangesExpected(componentConnector - .getParent())) { + boolean connectorChangesExpected = !currentDependencyTree + .noMoreChangesExpected(componentConnector); + boolean parentChangesExcpected = componentConnector.getParent() instanceof ComponentConnector + && !currentDependencyTree + .noMoreChangesExpected((ComponentConnector) componentConnector + .getParent()); + if (connectorChangesExpected || parentChangesExcpected) { delayedOverflowFixes.add(componentConnector); continue; } @@ -521,15 +518,8 @@ public class LayoutManager { } private void measureConnector(ComponentConnector connector) { - MeasuredSize measuredSize = getMeasuredSize(connector); - if (!isManagedLayout(connector) - && !isManagedLayout(connector.getParent()) - && elementResizeListeners.get(connector.getWidget() - .getElement()) == null && !measuredSize.hasDependents()) { - return; - } - Element element = connector.getWidget().getElement(); + MeasuredSize measuredSize = getMeasuredSize(connector); MeasureResult measureResult = measuredAndUpdate(element, measuredSize); if (measureResult.isChanged()) { @@ -569,11 +559,8 @@ public class LayoutManager { + " non connector elements"); } - int measureCount = 0; - private MeasureResult measuredAndUpdate(Element element, MeasuredSize measuredSize) { - measureCount++; MeasureResult measureResult = measuredSize.measure(element); if (measureResult.isChanged()) { notifyListenersAndDepdendents(element, @@ -1032,12 +1019,48 @@ public class LayoutManager { return getMeasuredSize(element, nullSize).getMarginLeft(); } - public int getMarginWidth(Element element) { - return getMeasuredSize(element, nullSize).getMarginWidth(); + /** + * Gets the combined top & bottom margin of the given element, provided that + * they have been measured. These elements are guaranteed to be measured: + * <ul> + * <li>ManagedLayotus and their child Connectors + * <li>Elements for which there is at least one ElementResizeListener + * <li>Elements for which at least one ManagedLayout has registered a + * dependency + * </ul> + * + * A negative number is returned if the element has not been measured. If 0 + * is returned, it might indicate that the element is not attached to the + * DOM. + * + * @param element + * the element to get the measured margin for + * @return the measured top+bottom margin of the element in pixels. + */ + public int getMarginHeight(Element element) { + return getMarginTop(element) + getMarginBottom(element); } - public int getMarginHeight(Element element) { - return getMeasuredSize(element, nullSize).getMarginHeight(); + /** + * Gets the combined left & right margin of the given element, provided that + * they have been measured. These elements are guaranteed to be measured: + * <ul> + * <li>ManagedLayotus and their child Connectors + * <li>Elements for which there is at least one ElementResizeListener + * <li>Elements for which at least one ManagedLayout has registered a + * dependency + * </ul> + * + * A negative number is returned if the element has not been measured. If 0 + * is returned, it might indicate that the element is not attached to the + * DOM. + * + * @param element + * the element to get the measured margin for + * @return the measured left+right margin of the element in pixels. + */ + public int getMarginWidth(Element element) { + return getMarginLeft(element) + getMarginRight(element); } /** @@ -1192,12 +1215,12 @@ public class LayoutManager { listeners.remove(listener); if (listeners.isEmpty()) { elementResizeListeners.remove(element); - stopMeasuringIfUnnecessary(element); + stopMeasuringIfUnecessary(element); } } } - private void stopMeasuringIfUnnecessary(Element element) { + private void stopMeasuringIfUnecessary(Element element) { if (!needsMeasure(element)) { measuredNonConnectorElements.remove(element); setMeasuredSize(element, null); @@ -1227,4 +1250,10 @@ public class LayoutManager { everythingNeedsMeasure = true; } + /** + * Clean measured sizes which are no longer needed. Only for IE8. + */ + protected void cleanMeasuredSizes() { + } + } diff --git a/src/com/vaadin/terminal/gwt/client/LayoutManagerIE8.java b/src/com/vaadin/terminal/gwt/client/LayoutManagerIE8.java index fc55007969..ea130779ea 100644 --- a/src/com/vaadin/terminal/gwt/client/LayoutManagerIE8.java +++ b/src/com/vaadin/terminal/gwt/client/LayoutManagerIE8.java @@ -4,43 +4,47 @@ package com.vaadin.terminal.gwt.client; import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Element; +import com.google.gwt.user.client.ui.RootPanel; public class LayoutManagerIE8 extends LayoutManager { - protected HashMap<Element, MeasuredSize> sizes = new HashMap<Element, MeasuredSize>(); + private Map<Element, MeasuredSize> measuredSizes = new HashMap<Element, MeasuredSize>(); @Override protected void setMeasuredSize(Element element, MeasuredSize measuredSize) { if (measuredSize != null) { - sizes.put(element, measuredSize); + measuredSizes.put(element, measuredSize); } else { - sizes.remove(element); + measuredSizes.remove(element); } } - // @Override - // protected native void setMeasuredSize(Element element, - // MeasuredSize measuredSize) - // IE8 cannot do delete element.vMeasuredSize, at least in the case when - // element is not attached to the document (e.g. when a caption is removed) - /*-{ - if (measuredSize) { - element.vMeasuredSize = measuredSize; - } else { - element.vMeasuredSize = undefined; - } - // }-*/; - @Override protected MeasuredSize getMeasuredSize(Element element, MeasuredSize defaultSize) { - MeasuredSize size = sizes.get(element); - if (size != null) { - return size; + MeasuredSize measured = measuredSizes.get(element); + if (measured != null) { + return measured; + } else { + return defaultSize; } - return defaultSize; } + @Override + protected void cleanMeasuredSizes() { + Document document = RootPanel.get().getElement().getOwnerDocument(); + + Iterator<Element> i = measuredSizes.keySet().iterator(); + while (i.hasNext()) { + Element e = i.next(); + if (e.getOwnerDocument() != document) { + i.remove(); + } + } + } } diff --git a/src/com/vaadin/terminal/gwt/client/ServerConnector.java b/src/com/vaadin/terminal/gwt/client/ServerConnector.java index 02b894e831..fcf5100c8e 100644 --- a/src/com/vaadin/terminal/gwt/client/ServerConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ServerConnector.java @@ -4,6 +4,7 @@ package com.vaadin.terminal.gwt.client; import java.util.Collection; +import java.util.List; import com.google.gwt.event.shared.GwtEvent; import com.google.web.bindery.event.shared.HandlerRegistration; @@ -94,4 +95,32 @@ public interface ServerConnector extends Connector { */ public void onUnregister(); + /** + * Returns the parent of this connector. Can be null for only the root + * connector. + * + * @return The parent of this connector, as set by + * {@link #setParent(ServerConnector)}. + */ + public ServerConnector getParent(); + + /** + * Sets the parent for this connector. This method should only be called by + * the framework to ensure that the connector hierarchy on the client side + * and the server side are in sync. + * <p> + * Note that calling this method does not fire a + * {@link ConnectorHierarchyChangeEvent}. The event is fired only when the + * whole hierarchy has been updated. + * + * @param parent + * The new parent of the connector + */ + public void setParent(ServerConnector parent); + + public void updateEnabledState(boolean enabledState); + + public void setChildren(List<ServerConnector> children); + + public List<ServerConnector> getChildren(); } diff --git a/src/com/vaadin/terminal/gwt/client/Util.java b/src/com/vaadin/terminal/gwt/client/Util.java index c392a0ba9c..d3cb54160d 100644 --- a/src/com/vaadin/terminal/gwt/client/Util.java +++ b/src/com/vaadin/terminal/gwt/client/Util.java @@ -812,13 +812,12 @@ public class Util { return idx; } - private static void printPaintablesInvocations( + private static void printConnectorInvocations( ArrayList<MethodInvocation> invocations, String id, ApplicationConnection c) { - ComponentConnector paintable = (ComponentConnector) ConnectorMap.get(c) - .getConnector(id); - if (paintable != null) { - VConsole.log("\t" + id + " (" + paintable.getClass() + ") :"); + ServerConnector connector = ConnectorMap.get(c).getConnector(id); + if (connector != null) { + VConsole.log("\t" + id + " (" + connector.getClass() + ") :"); for (MethodInvocation invocation : invocations) { Object[] parameters = invocation.getParameters(); String formattedParams = null; @@ -842,7 +841,7 @@ public class Util { + ")"); } } else { - VConsole.log("\t" + id + ": Warning: no corresponding paintable!"); + VConsole.log("\t" + id + ": Warning: no corresponding connector!"); } } @@ -858,14 +857,14 @@ public class Util { if (curId == null) { curId = id; } else if (!curId.equals(id)) { - printPaintablesInvocations(invocations, curId, c); + printConnectorInvocations(invocations, curId, c); invocations.clear(); curId = id; } invocations.add(loggedBurst.get(i)); } if (!invocations.isEmpty()) { - printPaintablesInvocations(invocations, curId, c); + printConnectorInvocations(invocations, curId, c); } } catch (Exception e) { VConsole.error(e); @@ -912,6 +911,22 @@ public class Util { } /** + * Find the element corresponding to the coordinates in the passed mouse + * event. Please note that this is not always the same as the target of the + * event e.g. if event capture is used. + * + * @param event + * the mouse event to get coordinates from + * @return the element at the coordinates of the event + */ + public static Element getElementUnderMouse(NativeEvent event) { + int pageX = getTouchOrMouseClientX(event); + int pageY = getTouchOrMouseClientY(event); + + return getElementFromPoint(pageX, pageY); + } + + /** * A helper method to return the client position from an event. Returns * position from either first changed touch (if touch event) or from the * event itself. diff --git a/src/com/vaadin/terminal/gwt/client/VDebugConsole.java b/src/com/vaadin/terminal/gwt/client/VDebugConsole.java index 5eaf78f255..09e939336e 100644 --- a/src/com/vaadin/terminal/gwt/client/VDebugConsole.java +++ b/src/com/vaadin/terminal/gwt/client/VDebugConsole.java @@ -560,23 +560,27 @@ public class VDebugConsole extends VOverlay implements Console { Set<ComponentConnector> zeroHeightComponents, ApplicationConnection ac) { for (final ComponentConnector paintable : zeroHeightComponents) { - final Widget layout = paintable.getParent().getWidget(); + final ServerConnector parent = paintable.getParent(); VerticalPanel errorDetails = new VerticalPanel(); errorDetails.add(new Label("" + Util.getSimpleName(paintable) - + " inside " + Util.getSimpleName(layout))); - final CheckBox emphasisInUi = new CheckBox( - "Emphasize components parent in UI (the actual component is not visible)"); - emphasisInUi.addClickHandler(new ClickHandler() { - public void onClick(ClickEvent event) { - if (paintable != null) { + + " inside " + Util.getSimpleName(parent))); + if (parent instanceof ComponentConnector) { + ComponentConnector parentComponent = (ComponentConnector) parent; + final Widget layout = parentComponent.getWidget(); + + final CheckBox emphasisInUi = new CheckBox( + "Emphasize components parent in UI (the actual component is not visible)"); + emphasisInUi.addClickHandler(new ClickHandler() { + public void onClick(ClickEvent event) { Element element2 = layout.getElement(); Widget.setStyleName(element2, "invalidlayout", - emphasisInUi.getValue()); + emphasisInUi.getValue().booleanValue()); } - } - }); - errorDetails.add(emphasisInUi); + }); + + errorDetails.add(emphasisInUi); + } panel.add(errorDetails); } } @@ -862,7 +866,7 @@ public class VDebugConsole extends VOverlay implements Console { log("================"); log("Connector hierarchy for Root: " + root.getState().getCaption() + " (" + root.getConnectorId() + ")"); - Set<ComponentConnector> connectorsInHierarchy = new HashSet<ComponentConnector>(); + Set<ServerConnector> connectorsInHierarchy = new HashSet<ServerConnector>(); SimpleTree rootHierachy = dumpConnectorHierarchy(root, "", connectorsInHierarchy); if (panel.isAttached()) { @@ -874,7 +878,7 @@ public class VDebugConsole extends VOverlay implements Console { Collection<? extends ServerConnector> registeredConnectors = connectorMap .getConnectors(); log("Sub windows:"); - Set<ComponentConnector> subWindowHierarchyConnectors = new HashSet<ComponentConnector>(); + Set<ServerConnector> subWindowHierarchyConnectors = new HashSet<ServerConnector>(); for (WindowConnector wc : root.getSubWindows()) { SimpleTree windowHierachy = dumpConnectorHierarchy(wc, "", subWindowHierarchyConnectors); @@ -908,14 +912,15 @@ public class VDebugConsole extends VOverlay implements Console { } - private SimpleTree dumpConnectorHierarchy( - final ComponentConnector connector, String indent, - Set<ComponentConnector> connectors) { + private SimpleTree dumpConnectorHierarchy(final ServerConnector connector, + String indent, Set<ServerConnector> connectors) { SimpleTree simpleTree = new SimpleTree(getConnectorString(connector)) { @Override protected void select(ClickEvent event) { super.select(event); - VUIDLBrowser.highlight(connector); + if (connector instanceof ComponentConnector) { + VUIDLBrowser.highlight((ComponentConnector) connector); + } } }; simpleTree.addDomHandler(new MouseOutHandler() { @@ -930,12 +935,8 @@ public class VDebugConsole extends VOverlay implements Console { consoleLog(msg); System.out.println(msg); - if (connector instanceof ComponentContainerConnector) { - for (ComponentConnector c : ((ComponentContainerConnector) connector) - .getChildren()) { - simpleTree.add(dumpConnectorHierarchy(c, indent + " ", - connectors)); - } + for (ServerConnector c : connector.getChildren()) { + simpleTree.add(dumpConnectorHierarchy(c, indent + " ", connectors)); } return simpleTree; } diff --git a/src/com/vaadin/terminal/gwt/client/VUIDLBrowser.java b/src/com/vaadin/terminal/gwt/client/VUIDLBrowser.java index 9fa973dc29..4230eda298 100644 --- a/src/com/vaadin/terminal/gwt/client/VUIDLBrowser.java +++ b/src/com/vaadin/terminal/gwt/client/VUIDLBrowser.java @@ -98,8 +98,8 @@ public class VUIDLBrowser extends SimpleTree { private String getNodeName(UIDL uidl, ApplicationConfiguration conf, int tag) { - Class<? extends ComponentConnector> widgetClassByDecodedTag = conf - .getWidgetClassByEncodedTag(tag); + Class<? extends ServerConnector> widgetClassByDecodedTag = conf + .getConnectorClassByEncodedTag(tag); if (widgetClassByDecodedTag == UnknownComponentConnector.class) { return conf.getUnknownServerClassNameByTag(tag) + "(NO CLIENT IMPLEMENTATION FOUND)"; diff --git a/src/com/vaadin/terminal/gwt/client/WidgetInstantiator.java b/src/com/vaadin/terminal/gwt/client/WidgetInstantiator.java index dd69883d58..0a4f92bc79 100644 --- a/src/com/vaadin/terminal/gwt/client/WidgetInstantiator.java +++ b/src/com/vaadin/terminal/gwt/client/WidgetInstantiator.java @@ -7,5 +7,5 @@ package com.vaadin.terminal.gwt.client; * A helper class used by WidgetMap implementation. Used by the generated code. */ interface WidgetInstantiator { - public ComponentConnector get(); + public ServerConnector get(); } diff --git a/src/com/vaadin/terminal/gwt/client/WidgetMap.java b/src/com/vaadin/terminal/gwt/client/WidgetMap.java index af84a11ced..b770414457 100644 --- a/src/com/vaadin/terminal/gwt/client/WidgetMap.java +++ b/src/com/vaadin/terminal/gwt/client/WidgetMap.java @@ -17,7 +17,7 @@ import com.vaadin.terminal.gwt.widgetsetutils.WidgetMapGenerator; */ abstract class WidgetMap { - protected static HashMap<Class, WidgetInstantiator> instmap = new HashMap<Class, WidgetInstantiator>(); + protected static HashMap<Class<? extends ServerConnector>, WidgetInstantiator> instmap = new HashMap<Class<? extends ServerConnector>, WidgetInstantiator>(); /** * Create a new instance of a connector based on its type. @@ -26,8 +26,8 @@ abstract class WidgetMap { * {@link ComponentConnector} class to instantiate * @return new instance of the connector */ - public ComponentConnector instantiate( - Class<? extends ComponentConnector> classType) { + public ServerConnector instantiate( + Class<? extends ServerConnector> classType) { return instmap.get(classType).get(); } @@ -39,7 +39,7 @@ abstract class WidgetMap { * fully qualified name of the server side component class * @return component connector class to use */ - public abstract Class<? extends ComponentConnector> getConnectorClassForServerSideClassName( + public abstract Class<? extends ServerConnector> getConnectorClassForServerSideClassName( String fullyqualifiedName); /** @@ -49,7 +49,7 @@ abstract class WidgetMap { * @return component connector class to load after the initial widgetset * loading */ - public abstract Class<? extends ComponentConnector>[] getDeferredLoadedWidgets(); + public abstract Class<? extends ServerConnector>[] getDeferredLoadedConnectors(); /** * Make sure the code for a (deferred or lazy) component connector type has @@ -60,6 +60,6 @@ abstract class WidgetMap { * component connector class */ public abstract void ensureInstantiator( - Class<? extends ComponentConnector> classType); + Class<? extends ServerConnector> classType); } diff --git a/src/com/vaadin/terminal/gwt/client/WidgetSet.java b/src/com/vaadin/terminal/gwt/client/WidgetSet.java index e47837296d..3d7e838c62 100644 --- a/src/com/vaadin/terminal/gwt/client/WidgetSet.java +++ b/src/com/vaadin/terminal/gwt/client/WidgetSet.java @@ -5,7 +5,7 @@ package com.vaadin.terminal.gwt.client; import com.google.gwt.core.client.GWT; -import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.communication.HasJavaScriptConnectorHelper; import com.vaadin.terminal.gwt.client.ui.UnknownComponentConnector; public class WidgetSet { @@ -18,29 +18,29 @@ public class WidgetSet { private WidgetMap widgetMap = GWT.create(WidgetMap.class); /** - * Create an uninitialized component that best matches given UIDL. The - * component must be a {@link Widget} that implements - * {@link ComponentConnector}. + * Create an uninitialized connector that best matches given UIDL. The + * connector must implement {@link ServerConnector}. * * @param tag - * component type tag for the component to create - * @param client - * the application connection that whishes to instantiate widget + * connector type tag for the connector to create + * @param conf + * the application configuration to use when creating the + * connector * - * @return New uninitialized and unregistered component that can paint given + * @return New uninitialized and unregistered connector that can paint given * UIDL. */ - public ComponentConnector createWidget(int tag, + public ServerConnector createConnector(int tag, ApplicationConfiguration conf) { /* * Yes, this (including the generated code in WidgetMap) may look very * odd code, but due the nature of GWT, we cannot do this any cleaner. * Luckily this is mostly written by WidgetSetGenerator, here are just - * some hacks. Extra instantiation code is needed if client side widget - * has no "native" counterpart on client side. + * some hacks. Extra instantiation code is needed if client side + * connector has no "native" counterpart on client side. */ - Class<? extends ComponentConnector> classType = resolveInheritedWidgetType( + Class<? extends ServerConnector> classType = resolveInheritedConnectorType( conf, tag); if (classType == null || classType == UnknownComponentConnector.class) { @@ -53,27 +53,32 @@ public class WidgetSet { /* * let the auto generated code instantiate this type */ - return widgetMap.instantiate(classType); + ServerConnector connector = widgetMap.instantiate(classType); + if (connector instanceof HasJavaScriptConnectorHelper) { + ((HasJavaScriptConnectorHelper) connector) + .getJavascriptConnectorHelper().setTag(tag); + } + return connector; } } - private Class<? extends ComponentConnector> resolveInheritedWidgetType( + private Class<? extends ServerConnector> resolveInheritedConnectorType( ApplicationConfiguration conf, int tag) { - Class<? extends ComponentConnector> classType = null; + Class<? extends ServerConnector> classType = null; Integer t = tag; do { - classType = resolveWidgetType(t, conf); + classType = resolveConnectorType(t, conf); t = conf.getParentTag(t); } while (classType == null && t != null); return classType; } - protected Class<? extends ComponentConnector> resolveWidgetType(int tag, + protected Class<? extends ServerConnector> resolveConnectorType(int tag, ApplicationConfiguration conf) { - Class<? extends ComponentConnector> widgetClass = conf - .getWidgetClassByEncodedTag(tag); + Class<? extends ServerConnector> connectorClass = conf + .getConnectorClassByEncodedTag(tag); - return widgetClass; + return connectorClass; } /** @@ -85,9 +90,9 @@ public class WidgetSet { * @param applicationConfiguration * @return */ - public Class<? extends ComponentConnector> getConnectorClassByTag(int tag, + public Class<? extends ServerConnector> getConnectorClassByTag(int tag, ApplicationConfiguration conf) { - Class<? extends ComponentConnector> connectorClass = null; + Class<? extends ServerConnector> connectorClass = null; Integer t = tag; do { String serverSideClassName = conf.getServerSideClassNameForTag(t); @@ -99,11 +104,11 @@ public class WidgetSet { return connectorClass; } - public Class<? extends ComponentConnector>[] getDeferredLoadedWidgets() { - return widgetMap.getDeferredLoadedWidgets(); + public Class<? extends ServerConnector>[] getDeferredLoadedConnectors() { + return widgetMap.getDeferredLoadedConnectors(); } - public void loadImplementation(Class<? extends ComponentConnector> nextType) { + public void loadImplementation(Class<? extends ServerConnector> nextType) { widgetMap.ensureInstantiator(nextType); } diff --git a/src/com/vaadin/terminal/gwt/client/communication/DiffJSONSerializer.java b/src/com/vaadin/terminal/gwt/client/communication/DiffJSONSerializer.java new file mode 100644 index 0000000000..29cb714828 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/communication/DiffJSONSerializer.java @@ -0,0 +1,19 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.communication; + +import com.google.gwt.json.client.JSONValue; +import com.vaadin.terminal.gwt.client.ApplicationConnection; + +public interface DiffJSONSerializer<T> extends JSONSerializer<T> { + /** + * Update the target object in place based on the passed JSON data. + * + * @param target + * @param jsonValue + * @param connection + */ + public void update(T target, Type type, JSONValue jsonValue, + ApplicationConnection connection); +} diff --git a/src/com/vaadin/terminal/gwt/client/communication/GeneratedRpcMethodProvider.java b/src/com/vaadin/terminal/gwt/client/communication/GeneratedRpcMethodProvider.java new file mode 100644 index 0000000000..c92466084c --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/communication/GeneratedRpcMethodProvider.java @@ -0,0 +1,20 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.communication; + +import java.util.Collection; + +/** + * Provides runtime data about client side RPC calls received from the server to + * the client-side code. + * + * A GWT generator is used to create an implementation of this class at + * run-time. + * + * @since 7.0 + */ +public interface GeneratedRpcMethodProvider { + + public Collection<RpcMethod> getGeneratedRpcMethods(); +} diff --git a/src/com/vaadin/terminal/gwt/client/communication/HasJavaScriptConnectorHelper.java b/src/com/vaadin/terminal/gwt/client/communication/HasJavaScriptConnectorHelper.java new file mode 100644 index 0000000000..a5191a5fed --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/communication/HasJavaScriptConnectorHelper.java @@ -0,0 +1,11 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.communication; + +import com.vaadin.terminal.gwt.client.JavaScriptConnectorHelper; + +public interface HasJavaScriptConnectorHelper { + public JavaScriptConnectorHelper getJavascriptConnectorHelper(); +} diff --git a/src/com/vaadin/terminal/gwt/client/communication/JSONSerializer.java b/src/com/vaadin/terminal/gwt/client/communication/JSONSerializer.java index f7b3df6b05..9820b6a895 100644 --- a/src/com/vaadin/terminal/gwt/client/communication/JSONSerializer.java +++ b/src/com/vaadin/terminal/gwt/client/communication/JSONSerializer.java @@ -34,14 +34,9 @@ public interface JSONSerializer<T> { * * @param jsonValue * JSON map from property name to property value - * @param target - * The object to write the deserialized values to - * @param idMapper - * mapper from paintable id to paintable, used to decode - * references to paintables * @return A deserialized object */ - T deserialize(JSONValue jsonValue, T target, ConnectorMap idMapper, + T deserialize(Type type, JSONValue jsonValue, ApplicationConnection connection); /** @@ -52,12 +47,8 @@ public interface JSONSerializer<T> { * * @param value * The object to serialize - * @param idMapper - * mapper from paintable id to paintable, used to decode - * references to paintables * @return A JSON serialized version of the object */ - JSONValue serialize(T value, ConnectorMap idMapper, - ApplicationConnection connection); + JSONValue serialize(T value, ApplicationConnection connection); } diff --git a/src/com/vaadin/terminal/gwt/client/communication/JsonDecoder.java b/src/com/vaadin/terminal/gwt/client/communication/JsonDecoder.java index 9ed20b6c79..23a2c30cd0 100644 --- a/src/com/vaadin/terminal/gwt/client/communication/JsonDecoder.java +++ b/src/com/vaadin/terminal/gwt/client/communication/JsonDecoder.java @@ -5,21 +5,20 @@ package com.vaadin.terminal.gwt.client.communication; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; 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.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.Connector; import com.vaadin.terminal.gwt.client.ConnectorMap; -import com.vaadin.terminal.gwt.client.ServerConnector; /** * Client side decoder for decodeing shared state and other values from JSON @@ -38,128 +37,177 @@ public class JsonDecoder { * Decode a JSON array with two elements (type and value) into a client-side * type, recursively if necessary. * - * @param jsonArray - * JSON array with two elements - * @param idMapper - * mapper between connector ID and {@link ServerConnector} - * objects + * @param jsonValue + * JSON value with encoded data * @param connection * reference to the current ApplicationConnection * @return decoded value (does not contain JSON types) */ - public static Object decodeValue(JSONArray jsonArray, Object target, - ConnectorMap idMapper, ApplicationConnection connection) { - String type = ((JSONString) jsonArray.get(0)).stringValue(); - return decodeValue(type, jsonArray.get(1), target, idMapper, connection); - } + public static Object decodeValue(Type type, JSONValue jsonValue, + Object target, ApplicationConnection connection) { - private static Object decodeValue(String variableType, JSONValue value, - Object target, ConnectorMap idMapper, - ApplicationConnection connection) { - Object val = null; - // TODO type checks etc. - if (JsonEncoder.VTYPE_NULL.equals(variableType)) { - val = null; - } else if (JsonEncoder.VTYPE_ARRAY.equals(variableType)) { - val = decodeArray((JSONArray) value, idMapper, connection); - } else if (JsonEncoder.VTYPE_MAP.equals(variableType)) { - val = decodeMap((JSONObject) value, idMapper, connection); - } else if (JsonEncoder.VTYPE_LIST.equals(variableType)) { - val = decodeList((JSONArray) value, idMapper, connection); - } else if (JsonEncoder.VTYPE_SET.equals(variableType)) { - val = decodeSet((JSONArray) value, idMapper, connection); - } else if (JsonEncoder.VTYPE_STRINGARRAY.equals(variableType)) { - val = decodeStringArray((JSONArray) value); - } else if (JsonEncoder.VTYPE_STRING.equals(variableType)) { - val = ((JSONString) value).stringValue(); - } else if (JsonEncoder.VTYPE_INTEGER.equals(variableType)) { + // Null is null, regardless of type + if (jsonValue.isNull() != null) { + return null; + } + + String baseTypeName = type.getBaseTypeName(); + if (Map.class.getName().equals(baseTypeName) + || HashMap.class.getName().equals(baseTypeName)) { + return decodeMap(type, jsonValue, connection); + } else if (List.class.getName().equals(baseTypeName) + || ArrayList.class.getName().equals(baseTypeName)) { + return decodeList(type, (JSONArray) jsonValue, connection); + } else if (Set.class.getName().equals(baseTypeName)) { + return decodeSet(type, (JSONArray) jsonValue, connection); + } else if (String.class.getName().equals(baseTypeName)) { + return ((JSONString) jsonValue).stringValue(); + } else if (Integer.class.getName().equals(baseTypeName)) { + return Integer.valueOf(String.valueOf(jsonValue)); + } else if (Long.class.getName().equals(baseTypeName)) { + // TODO handle properly + return Long.valueOf(String.valueOf(jsonValue)); + } else if (Float.class.getName().equals(baseTypeName)) { // TODO handle properly - val = Integer.valueOf(String.valueOf(value)); - } else if (JsonEncoder.VTYPE_LONG.equals(variableType)) { + return Float.valueOf(String.valueOf(jsonValue)); + } else if (Double.class.getName().equals(baseTypeName)) { // TODO handle properly - val = Long.valueOf(String.valueOf(value)); - } else if (JsonEncoder.VTYPE_FLOAT.equals(variableType)) { + return Double.valueOf(String.valueOf(jsonValue)); + } else if (Boolean.class.getName().equals(baseTypeName)) { // TODO handle properly - val = Float.valueOf(String.valueOf(value)); - } else if (JsonEncoder.VTYPE_DOUBLE.equals(variableType)) { + return Boolean.valueOf(String.valueOf(jsonValue)); + } else if (Byte.class.getName().equals(baseTypeName)) { // TODO handle properly - val = Double.valueOf(String.valueOf(value)); - } else if (JsonEncoder.VTYPE_BOOLEAN.equals(variableType)) { + return Byte.valueOf(String.valueOf(jsonValue)); + } else if (Character.class.getName().equals(baseTypeName)) { // TODO handle properly - val = Boolean.valueOf(String.valueOf(value)); - } else if (JsonEncoder.VTYPE_CONNECTOR.equals(variableType)) { - val = idMapper.getConnector(((JSONString) value).stringValue()); + return Character.valueOf(((JSONString) jsonValue).stringValue() + .charAt(0)); + } else if (Connector.class.getName().equals(baseTypeName)) { + return ConnectorMap.get(connection).getConnector( + ((JSONString) jsonValue).stringValue()); } else { - return decodeObject(variableType, value, target, idMapper, - connection); + return decodeObject(type, jsonValue, target, connection); } - - return val; } - private static Object decodeObject(String variableType, - JSONValue encodedValue, Object target, ConnectorMap idMapper, - ApplicationConnection connection) { - // object, class name as type + private static Object decodeObject(Type type, JSONValue jsonValue, + Object target, ApplicationConnection connection) { JSONSerializer<Object> serializer = connection.getSerializerMap() - .getSerializer(variableType); + .getSerializer(type.getBaseTypeName()); // TODO handle case with no serializer found - Object object = serializer.deserialize(encodedValue, target, idMapper, - connection); - return object; + // Currently getSerializer throws exception if not found + + if (target != null && serializer instanceof DiffJSONSerializer<?>) { + DiffJSONSerializer<Object> diffSerializer = (DiffJSONSerializer<Object>) serializer; + diffSerializer.update(target, type, jsonValue, connection); + return target; + } else { + Object object = serializer.deserialize(type, jsonValue, connection); + return object; + } } - private static Map<Object, Object> decodeMap(JSONObject jsonMap, - ConnectorMap idMapper, ApplicationConnection connection) { - HashMap<Object, Object> map = new HashMap<Object, Object>(); - Iterator<String> it = jsonMap.keySet().iterator(); - while (it.hasNext()) { - String key = it.next(); - JSONArray encodedKey = (JSONArray) JSONParser.parseStrict(key); - JSONArray encodedValue = (JSONArray) jsonMap.get(key); - Object decodedKey = decodeValue(encodedKey, null, idMapper, + private static Map<Object, Object> decodeMap(Type type, JSONValue jsonMap, + ApplicationConnection connection) { + // Client -> server encodes empty map as an empty array because of + // #8906. Do the same for server -> client to maintain symmetry. + if (jsonMap instanceof JSONArray) { + JSONArray array = (JSONArray) jsonMap; + if (array.size() == 0) { + return new HashMap<Object, Object>(); + } + } + + Type keyType = type.getParameterTypes()[0]; + Type valueType = type.getParameterTypes()[1]; + + if (keyType.getBaseTypeName().equals(String.class.getName())) { + return decodeStringMap(valueType, jsonMap, connection); + } else if (keyType.getBaseTypeName().equals(Connector.class.getName())) { + return decodeConnectorMap(valueType, jsonMap, connection); + } else { + return decodeObjectMap(keyType, valueType, jsonMap, connection); + } + } + + private static Map<Object, Object> decodeObjectMap(Type keyType, + Type valueType, JSONValue jsonValue, + ApplicationConnection connection) { + Map<Object, Object> map = new HashMap<Object, Object>(); + + JSONArray mapArray = (JSONArray) jsonValue; + JSONArray keys = (JSONArray) mapArray.get(0); + JSONArray values = (JSONArray) mapArray.get(1); + + assert (keys.size() == values.size()); + + for (int i = 0; i < keys.size(); i++) { + Object decodedKey = decodeValue(keyType, keys.get(i), null, connection); - Object decodedValue = decodeValue(encodedValue, null, idMapper, + Object decodedValue = decodeValue(valueType, values.get(i), null, connection); + map.put(decodedKey, decodedValue); } + return map; } - private static String[] decodeStringArray(JSONArray jsonArray) { - int size = jsonArray.size(); - List<String> tokens = new ArrayList<String>(size); - for (int i = 0; i < size; ++i) { - tokens.add(String.valueOf(jsonArray.get(i))); + private static Map<Object, Object> decodeConnectorMap(Type valueType, + JSONValue jsonValue, ApplicationConnection connection) { + Map<Object, Object> map = new HashMap<Object, Object>(); + + JSONObject jsonMap = (JSONObject) jsonValue; + ConnectorMap connectorMap = ConnectorMap.get(connection); + + for (String connectorId : jsonMap.keySet()) { + Object value = decodeValue(valueType, jsonMap.get(connectorId), + null, connection); + map.put(connectorMap.getConnector(connectorId), value); } - return tokens.toArray(new String[tokens.size()]); + + return map; } - private static Object[] decodeArray(JSONArray jsonArray, - ConnectorMap idMapper, ApplicationConnection connection) { - List<Object> list = decodeList(jsonArray, idMapper, connection); - return list.toArray(new Object[list.size()]); + private static Map<Object, Object> decodeStringMap(Type valueType, + JSONValue jsonValue, ApplicationConnection connection) { + Map<Object, Object> map = new HashMap<Object, Object>(); + + JSONObject jsonMap = (JSONObject) jsonValue; + + for (String key : jsonMap.keySet()) { + Object value = decodeValue(valueType, jsonMap.get(key), null, + connection); + map.put(key, value); + } + + return map; } - private static List<Object> decodeList(JSONArray jsonArray, - ConnectorMap idMapper, ApplicationConnection connection) { + private static List<Object> decodeList(Type type, JSONArray jsonArray, + ApplicationConnection connection) { List<Object> tokens = new ArrayList<Object>(); - for (int i = 0; i < jsonArray.size(); ++i) { - // each entry always has two elements: type and value - JSONArray entryArray = (JSONArray) jsonArray.get(i); - tokens.add(decodeValue(entryArray, null, idMapper, connection)); - } + decodeIntoCollection(type.getParameterTypes()[0], jsonArray, + connection, tokens); return tokens; } - private static Set<Object> decodeSet(JSONArray jsonArray, - ConnectorMap idMapper, ApplicationConnection connection) { + private static Set<Object> decodeSet(Type type, JSONArray jsonArray, + ApplicationConnection connection) { Set<Object> tokens = new HashSet<Object>(); + decodeIntoCollection(type.getParameterTypes()[0], jsonArray, + connection, tokens); + return tokens; + } + + private static void decodeIntoCollection(Type childType, + JSONArray jsonArray, ApplicationConnection connection, + Collection<Object> tokens) { for (int i = 0; i < jsonArray.size(); ++i) { // each entry always has two elements: type and value - JSONArray entryArray = (JSONArray) jsonArray.get(i); - tokens.add(decodeValue(entryArray, null, idMapper, connection)); + JSONValue entryValue = jsonArray.get(i); + tokens.add(decodeValue(childType, entryValue, null, connection)); } - return tokens; } } diff --git a/src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java b/src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java index f09536a9f7..925f0b6272 100644 --- a/src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java +++ b/src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java @@ -7,17 +7,18 @@ package com.vaadin.terminal.gwt.client.communication; import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import com.google.gwt.json.client.JSONArray; import com.google.gwt.json.client.JSONBoolean; import com.google.gwt.json.client.JSONNull; +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.vaadin.terminal.gwt.client.ApplicationConnection; import com.vaadin.terminal.gwt.client.Connector; -import com.vaadin.terminal.gwt.client.ConnectorMap; /** * Encoder for converting RPC parameters and other values to JSON for transfer @@ -52,57 +53,49 @@ public class JsonEncoder { * * @param value * value to convert - * @param connectorMap - * mapper from connectors to connector IDs * @param connection * @return JSON representation of the value */ public static JSONValue encode(Object value, - boolean restrictToInternalTypes, ConnectorMap connectorMap, - ApplicationConnection connection) { + boolean restrictToInternalTypes, ApplicationConnection connection) { if (null == value) { - return combineTypeAndValue(VTYPE_NULL, JSONNull.getInstance()); + return JSONNull.getInstance(); + } else if (value instanceof JSONValue) { + return (JSONValue) value; } else if (value instanceof String[]) { String[] array = (String[]) value; JSONArray jsonArray = new JSONArray(); for (int i = 0; i < array.length; ++i) { jsonArray.set(i, new JSONString(array[i])); } - return combineTypeAndValue(VTYPE_STRINGARRAY, jsonArray); + return jsonArray; } else if (value instanceof String) { - return combineTypeAndValue(VTYPE_STRING, new JSONString( - (String) value)); + return new JSONString((String) value); } else if (value instanceof Boolean) { - return combineTypeAndValue(VTYPE_BOOLEAN, - JSONBoolean.getInstance((Boolean) value)); + return JSONBoolean.getInstance((Boolean) value); + } else if (value instanceof Byte) { + return new JSONNumber((Byte) value); + } else if (value instanceof Character) { + return new JSONString(String.valueOf(value)); } else if (value instanceof Object[]) { return encodeObjectArray((Object[]) value, restrictToInternalTypes, - connectorMap, connection); + connection); } else if (value instanceof Enum) { - if (restrictToInternalTypes) { - // Enums are encoded as strings in Vaadin 6 so we still do that - // for backwards copmatibility. - return encode(value.toString(), restrictToInternalTypes, - connectorMap, connection); - } else { - Enum e = (Enum) value; - return encodeEnum(e, connectorMap, connection); - } + return encodeEnum((Enum<?>) value, connection); } else if (value instanceof Map) { - return encodeMap((Map) value, restrictToInternalTypes, - connectorMap, connection); + return encodeMap((Map) value, restrictToInternalTypes, connection); } else if (value instanceof Connector) { Connector connector = (Connector) value; - return combineTypeAndValue(VTYPE_CONNECTOR, new JSONString( - connector.getConnectorId())); + return new JSONString(connector.getConnectorId()); } else if (value instanceof Collection) { return encodeCollection((Collection) value, - restrictToInternalTypes, connectorMap, connection); + restrictToInternalTypes, connection); + } else if (value instanceof UidlValue) { + return encodeVariableChange((UidlValue) value, connection); } else { String transportType = getTransportType(value); if (transportType != null) { - return combineTypeAndValue(transportType, - new JSONString(String.valueOf(value))); + return new JSONString(String.valueOf(value)); } else { // Try to find a generated serializer object, class name is the // type @@ -111,61 +104,135 @@ public class JsonEncoder { .getSerializer(transportType); // TODO handle case with no serializer found - return combineTypeAndValue(transportType, - serializer.serialize(value, connectorMap, connection)); + return serializer.serialize(value, connection); } } } + private static JSONValue encodeVariableChange(UidlValue uidlValue, + ApplicationConnection connection) { + Object value = uidlValue.getValue(); + + JSONArray jsonArray = new JSONArray(); + jsonArray.set(0, new JSONString(getTransportType(value))); + jsonArray.set(1, encode(value, true, connection)); + + return jsonArray; + } + private static JSONValue encodeMap(Map<Object, Object> map, - boolean restrictToInternalTypes, ConnectorMap connectorMap, + boolean restrictToInternalTypes, ApplicationConnection connection) { + /* + * As we have no info about declared types, we instead select encoding + * scheme based on actual type of first key. We can't do this if there's + * no first key, so instead we send some special value that the + * server-side decoding must check for. (see #8906) + */ + if (map.isEmpty()) { + return new JSONArray(); + } + + Object firstKey = map.keySet().iterator().next(); + if (firstKey instanceof String) { + return encodeStringMap(map, restrictToInternalTypes, connection); + } else if (restrictToInternalTypes) { + throw new IllegalStateException( + "Only string keys supported for legacy maps"); + } else if (firstKey instanceof Connector) { + return encodeConenctorMap(map, connection); + } else { + return encodeObjectMap(map, connection); + } + } + + private static JSONValue encodeObjectMap(Map<Object, Object> map, + ApplicationConnection connection) { + JSONArray keys = new JSONArray(); + JSONArray values = new JSONArray(); + for (Entry<?, ?> entry : map.entrySet()) { + // restrictToInternalTypes always false if we end up here + keys.set(keys.size(), encode(entry.getKey(), false, connection)); + values.set(values.size(), + encode(entry.getValue(), false, connection)); + } + + JSONArray keysAndValues = new JSONArray(); + keysAndValues.set(0, keys); + keysAndValues.set(1, values); + + return keysAndValues; + } + + private static JSONValue encodeConenctorMap(Map<Object, Object> map, ApplicationConnection connection) { JSONObject jsonMap = new JSONObject(); - for (Object mapKey : map.keySet()) { - Object mapValue = map.get(mapKey); - JSONValue encodedKey = encode(mapKey, restrictToInternalTypes, - connectorMap, connection); - JSONValue encodedValue = encode(mapValue, restrictToInternalTypes, - connectorMap, connection); - jsonMap.put(encodedKey.toString(), encodedValue); + + for (Entry<?, ?> entry : map.entrySet()) { + Connector connector = (Connector) entry.getKey(); + + // restrictToInternalTypes always false if we end up here + JSONValue encodedValue = encode(entry.getValue(), false, connection); + + jsonMap.put(connector.getConnectorId(), encodedValue); + } + + return jsonMap; + } + + private static JSONValue encodeStringMap(Map<Object, Object> map, + boolean restrictToInternalTypes, ApplicationConnection connection) { + JSONObject jsonMap = new JSONObject(); + + for (Entry<?, ?> entry : map.entrySet()) { + String key = (String) entry.getKey(); + Object value = entry.getValue(); + + if (restrictToInternalTypes) { + value = new UidlValue(value); + } + + JSONValue encodedValue = encode(value, restrictToInternalTypes, + connection); + + jsonMap.put(key, encodedValue); } - return combineTypeAndValue(VTYPE_MAP, jsonMap); + + return jsonMap; } - private static JSONValue encodeEnum(Enum e, ConnectorMap connectorMap, + private static JSONValue encodeEnum(Enum<?> e, ApplicationConnection connection) { - return combineTypeAndValue(e.getClass().getName(), - new JSONString(e.toString())); + return new JSONString(e.toString()); } private static JSONValue encodeObjectArray(Object[] array, - boolean restrictToInternalTypes, ConnectorMap connectorMap, - ApplicationConnection connection) { + boolean restrictToInternalTypes, ApplicationConnection connection) { JSONArray jsonArray = new JSONArray(); for (int i = 0; i < array.length; ++i) { // TODO handle object graph loops? - jsonArray.set( - i, - encode(array[i], restrictToInternalTypes, connectorMap, - connection)); + Object value = array[i]; + if (restrictToInternalTypes) { + value = new UidlValue(value); + } + jsonArray + .set(i, encode(value, restrictToInternalTypes, connection)); } - return combineTypeAndValue(VTYPE_ARRAY, jsonArray); + return jsonArray; } private static JSONValue encodeCollection(Collection collection, - boolean restrictToInternalTypes, ConnectorMap connectorMap, - ApplicationConnection connection) { + boolean restrictToInternalTypes, ApplicationConnection connection) { JSONArray jsonArray = new JSONArray(); int idx = 0; for (Object o : collection) { JSONValue encodedObject = encode(o, restrictToInternalTypes, - connectorMap, connection); + connection); jsonArray.set(idx++, encodedObject); } if (collection instanceof Set) { - return combineTypeAndValue(VTYPE_SET, jsonArray); + return jsonArray; } else if (collection instanceof List) { - return combineTypeAndValue(VTYPE_LIST, jsonArray); + return jsonArray; } else { throw new RuntimeException("Unsupport collection type: " + collection.getClass().getName()); @@ -173,13 +240,6 @@ public class JsonEncoder { } - private static JSONValue combineTypeAndValue(String type, JSONValue value) { - JSONArray outerArray = new JSONArray(); - outerArray.set(0, new JSONString(type)); - outerArray.set(1, value); - return outerArray; - } - /** * Returns the transport type for the given value. Only returns a transport * type for internally handled values. @@ -216,6 +276,9 @@ public class JsonEncoder { return VTYPE_ARRAY; } else if (value instanceof Map) { return VTYPE_MAP; + } else if (value instanceof Enum<?>) { + // Enum value is processed as a string + return VTYPE_STRING; } return null; } diff --git a/src/com/vaadin/terminal/gwt/client/communication/RpcManager.java b/src/com/vaadin/terminal/gwt/client/communication/RpcManager.java index 302e6eaa55..07d6292ce2 100644 --- a/src/com/vaadin/terminal/gwt/client/communication/RpcManager.java +++ b/src/com/vaadin/terminal/gwt/client/communication/RpcManager.java @@ -4,9 +4,17 @@ package com.vaadin.terminal.gwt.client.communication; -import java.io.Serializable; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import com.google.gwt.core.client.GWT; +import com.google.gwt.json.client.JSONArray; +import com.google.gwt.json.client.JSONString; +import com.vaadin.terminal.gwt.client.ApplicationConnection; import com.vaadin.terminal.gwt.client.ConnectorMap; +import com.vaadin.terminal.gwt.client.ServerConnector; +import com.vaadin.terminal.gwt.client.VConsole; /** * Client side RPC manager that can invoke methods based on RPC calls received @@ -17,16 +25,98 @@ import com.vaadin.terminal.gwt.client.ConnectorMap; * * @since 7.0 */ -public interface RpcManager extends Serializable { +public class RpcManager { + + private final Map<String, RpcMethod> methodMap = new HashMap<String, RpcMethod>(); + + public RpcManager() { + GeneratedRpcMethodProvider provider = GWT + .create(GeneratedRpcMethodProvider.class); + Collection<RpcMethod> methods = provider.getGeneratedRpcMethods(); + for (RpcMethod rpcMethod : methods) { + methodMap.put( + rpcMethod.getInterfaceName() + "." + + rpcMethod.getMethodName(), rpcMethod); + } + } + /** * Perform server to client RPC invocation. * * @param invocation * method to invoke - * @param connectorMap - * mapper used to find Connector for the method call and any - * connectors referenced in parameters */ public void applyInvocation(MethodInvocation invocation, - ConnectorMap connectorMap); + ServerConnector connector) { + String signature = getSignature(invocation); + + RpcMethod rpcMethod = getRpcMethod(signature); + Collection<ClientRpc> implementations = connector + .getRpcImplementations(invocation.getInterfaceName()); + for (ClientRpc clientRpc : implementations) { + rpcMethod.applyInvocation(clientRpc, invocation.getParameters()); + } + } + + private RpcMethod getRpcMethod(String signature) { + RpcMethod rpcMethod = methodMap.get(signature); + if (rpcMethod == null) { + throw new IllegalStateException("There is no information about " + + signature + + ". Did you remember to compile the right widgetset?"); + } + return rpcMethod; + } + + private static String getSignature(MethodInvocation invocation) { + return invocation.getInterfaceName() + "." + invocation.getMethodName(); + } + + public Type[] getParameterTypes(MethodInvocation invocation) { + return getRpcMethod(getSignature(invocation)).getParameterTypes(); + } + + public void parseAndApplyInvocation(JSONArray rpcCall, + ApplicationConnection connection) { + ConnectorMap connectorMap = ConnectorMap.get(connection); + + String connectorId = ((JSONString) rpcCall.get(0)).stringValue(); + String interfaceName = ((JSONString) rpcCall.get(1)).stringValue(); + String methodName = ((JSONString) rpcCall.get(2)).stringValue(); + JSONArray parametersJson = (JSONArray) rpcCall.get(3); + + ServerConnector connector = connectorMap.getConnector(connectorId); + + MethodInvocation invocation = new MethodInvocation(connectorId, + interfaceName, methodName); + if (connector instanceof HasJavaScriptConnectorHelper) { + ((HasJavaScriptConnectorHelper) connector) + .getJavascriptConnectorHelper().invokeJsRpc(invocation, + parametersJson); + } else { + if (connector == null) { + throw new IllegalStateException("Target connector (" + + connector + ") not found for RCC to " + + getSignature(invocation)); + } + + parseMethodParameters(invocation, parametersJson, connection); + VConsole.log("Server to client RPC call: " + invocation); + applyInvocation(invocation, connector); + } + } + + private void parseMethodParameters(MethodInvocation methodInvocation, + JSONArray parametersJson, ApplicationConnection connection) { + Type[] parameterTypes = getParameterTypes(methodInvocation); + + Object[] parameters = new Object[parametersJson.size()]; + for (int j = 0; j < parametersJson.size(); ++j) { + parameters[j] = JsonDecoder.decodeValue(parameterTypes[j], + parametersJson.get(j), null, connection); + } + + methodInvocation.setParameters(parameters); + } + } diff --git a/src/com/vaadin/terminal/gwt/client/communication/RpcMethod.java b/src/com/vaadin/terminal/gwt/client/communication/RpcMethod.java new file mode 100644 index 0000000000..abdcf73e2c --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/communication/RpcMethod.java @@ -0,0 +1,32 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.communication; + +public abstract class RpcMethod { + private String interfaceName; + private String methodName; + private Type[] parameterTypes; + + public RpcMethod(String interfaceName, String methodName, + Type... parameterTypes) { + this.interfaceName = interfaceName; + this.methodName = methodName; + this.parameterTypes = parameterTypes; + } + + public String getInterfaceName() { + return interfaceName; + } + + public String getMethodName() { + return methodName; + } + + public Type[] getParameterTypes() { + return parameterTypes; + } + + public abstract void applyInvocation(ClientRpc target, Object... parameters); + +} diff --git a/src/com/vaadin/terminal/gwt/client/communication/SharedState.java b/src/com/vaadin/terminal/gwt/client/communication/SharedState.java index 266d6bcbf2..b087907f9e 100644 --- a/src/com/vaadin/terminal/gwt/client/communication/SharedState.java +++ b/src/com/vaadin/terminal/gwt/client/communication/SharedState.java @@ -38,4 +38,30 @@ import com.vaadin.terminal.gwt.client.ui.AbstractComponentConnector; * @since 7.0 */ public class SharedState implements Serializable { + + private boolean enabled = true; + + /** + * Returns true if the component is enabled. + * + * @see com.vaadin.ui.Component#isEnabled() + * + * @return true if the component is enabled + */ + public boolean isEnabled() { + return enabled; + } + + /** + * Enables or disables the component. + * + * @see com.vaadin.ui.Component#setEnabled(boolean) + * + * @param enabled + * new mode for the component + */ + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + } diff --git a/src/com/vaadin/terminal/gwt/client/communication/Type.java b/src/com/vaadin/terminal/gwt/client/communication/Type.java new file mode 100644 index 0000000000..dc33f760ff --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/communication/Type.java @@ -0,0 +1,40 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.communication; + +public class Type { + private final String baseTypeName; + private final Type[] parameterTypes; + + public Type(String baseTypeName, Type[] parameterTypes) { + this.baseTypeName = baseTypeName; + this.parameterTypes = parameterTypes; + } + + public String getBaseTypeName() { + return baseTypeName; + } + + public Type[] getParameterTypes() { + return parameterTypes; + } + + @Override + public String toString() { + String string = baseTypeName; + if (parameterTypes != null) { + string += '<'; + for (int i = 0; i < parameterTypes.length; i++) { + if (i != 0) { + string += ','; + } + string += parameterTypes[i].toString(); + } + string += '>'; + } + + return string; + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/communication/URLReference_Serializer.java b/src/com/vaadin/terminal/gwt/client/communication/URLReference_Serializer.java index 2ee7df7f6d..bab0f385ed 100644 --- a/src/com/vaadin/terminal/gwt/client/communication/URLReference_Serializer.java +++ b/src/com/vaadin/terminal/gwt/client/communication/URLReference_Serializer.java @@ -4,32 +4,34 @@ package com.vaadin.terminal.gwt.client.communication; import com.google.gwt.core.client.GWT; -import com.google.gwt.json.client.JSONArray; import com.google.gwt.json.client.JSONObject; import com.google.gwt.json.client.JSONValue; import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.ConnectorMap; public class URLReference_Serializer implements JSONSerializer<URLReference> { - public URLReference deserialize(JSONValue jsonValue, URLReference target, - ConnectorMap idMapper, ApplicationConnection connection) { + // setURL() -> uRL as first char becomes lower case... + private static final String URL_FIELD = "uRL"; + + public URLReference deserialize(Type type, JSONValue jsonValue, + ApplicationConnection connection) { URLReference reference = GWT.create(URLReference.class); JSONObject json = (JSONObject) jsonValue; - if (json.containsKey("URL")) { - JSONArray jsonURL = (JSONArray) json.get("URL"); - String URL = (String) JsonDecoder.decodeValue(jsonURL, null, - idMapper, connection); + if (json.containsKey(URL_FIELD)) { + JSONValue jsonURL = json.get(URL_FIELD); + String URL = (String) JsonDecoder.decodeValue( + new Type(String.class.getName(), null), jsonURL, null, + connection); reference.setURL(connection.translateVaadinUri(URL)); } return reference; } - public JSONValue serialize(URLReference value, ConnectorMap idMapper, + public JSONValue serialize(URLReference value, ApplicationConnection connection) { JSONObject json = new JSONObject(); - json.put("URL", - JsonEncoder.encode(value.getURL(), true, idMapper, connection)); + json.put(URL_FIELD, + JsonEncoder.encode(value.getURL(), true, connection)); return json; } diff --git a/src/com/vaadin/terminal/gwt/client/communication/UidlValue.java b/src/com/vaadin/terminal/gwt/client/communication/UidlValue.java new file mode 100644 index 0000000000..2a21074037 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/communication/UidlValue.java @@ -0,0 +1,25 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.communication; + +import java.io.Serializable; + +public class UidlValue implements Serializable { + private Object value; + + public UidlValue(Object value) { + this.value = value; + } + + public Object getValue() { + return value; + } + + @Override + public String toString() { + return "" + value; + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/extensions/AbstractExtensionConnector.java b/src/com/vaadin/terminal/gwt/client/extensions/AbstractExtensionConnector.java new file mode 100644 index 0000000000..bcefcf05cb --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/extensions/AbstractExtensionConnector.java @@ -0,0 +1,27 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.extensions; + +import com.vaadin.terminal.gwt.client.ServerConnector; +import com.vaadin.terminal.gwt.client.ui.AbstractConnector; + +public abstract class AbstractExtensionConnector extends AbstractConnector { + @Override + public void setParent(ServerConnector parent) { + ServerConnector oldParent = getParent(); + if (oldParent != null && oldParent != parent) { + throw new IllegalStateException( + "An extension can not be moved from one parent to another."); + } + + super.setParent(parent); + + extend(parent); + } + + protected void extend(ServerConnector target) { + // Default does nothing + } +} diff --git a/src/com/vaadin/terminal/gwt/client/extensions/javascriptmanager/ExecuteJavaScriptRpc.java b/src/com/vaadin/terminal/gwt/client/extensions/javascriptmanager/ExecuteJavaScriptRpc.java new file mode 100644 index 0000000000..f1185586d5 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/extensions/javascriptmanager/ExecuteJavaScriptRpc.java @@ -0,0 +1,11 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.extensions.javascriptmanager; + +import com.vaadin.terminal.gwt.client.communication.ClientRpc; + +public interface ExecuteJavaScriptRpc extends ClientRpc { + public void executeJavaScript(String script); +} diff --git a/src/com/vaadin/terminal/gwt/client/extensions/javascriptmanager/JavaScriptManagerConnector.java b/src/com/vaadin/terminal/gwt/client/extensions/javascriptmanager/JavaScriptManagerConnector.java new file mode 100644 index 0000000000..8656783a86 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/extensions/javascriptmanager/JavaScriptManagerConnector.java @@ -0,0 +1,119 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.extensions.javascriptmanager; + +import java.util.HashSet; +import java.util.Set; + +import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.core.client.JsArray; +import com.google.gwt.json.client.JSONArray; +import com.vaadin.terminal.gwt.client.communication.MethodInvocation; +import com.vaadin.terminal.gwt.client.communication.StateChangeEvent; +import com.vaadin.terminal.gwt.client.extensions.AbstractExtensionConnector; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.ui.JavaScript; + +@Connect(JavaScript.class) +public class JavaScriptManagerConnector extends AbstractExtensionConnector { + private Set<String> currentNames = new HashSet<String>(); + + @Override + protected void init() { + registerRpc(ExecuteJavaScriptRpc.class, new ExecuteJavaScriptRpc() { + public void executeJavaScript(String Script) { + eval(Script); + } + }); + } + + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); + + Set<String> newNames = getState().getNames(); + + // Current names now only contains orphan callbacks + currentNames.removeAll(newNames); + + for (String name : currentNames) { + removeCallback(name); + } + + currentNames = new HashSet<String>(newNames); + for (String name : newNames) { + addCallback(name); + } + } + + // TODO Ensure we don't overwrite anything (important) in $wnd + private native void addCallback(String name) + /*-{ + var m = this; + var target = $wnd; + var parts = name.split('.'); + + for(var i = 0; i < parts.length - 1; i++) { + var part = parts[i]; + if (target[part] === undefined) { + target[part] = {}; + } + target = target[part]; + } + + target[parts[parts.length - 1]] = $entry(function() { + //Must make a copy because arguments is an array-like object (not instanceof Array), causing suboptimal JSON encoding + var args = Array.prototype.slice.call(arguments, 0); + m.@com.vaadin.terminal.gwt.client.extensions.javascriptmanager.JavaScriptManagerConnector::sendRpc(Ljava/lang/String;Lcom/google/gwt/core/client/JsArray;)(name, args); + }); + }-*/; + + // TODO only remove what we actually added + // TODO We might leave empty objects behind, but there's no good way of + // knowing whether they are unused + private native void removeCallback(String name) + /*-{ + var target = $wnd; + var parts = name.split('.'); + + for(var i = 0; i < parts.length - 1; i++) { + var part = parts[i]; + if (target[part] === undefined) { + $wnd.console.log(part,'not defined in',target); + // No longer attached -> nothing more to do + return; + } + target = target[part]; + } + + $wnd.console.log('removing',parts[parts.length - 1],'from',target); + delete target[parts[parts.length - 1]]; + }-*/; + + private static native void eval(String script) + /*-{ + if(script) { + (new $wnd.Function(script)).apply($wnd); + } + }-*/; + + public void sendRpc(String name, JsArray<JavaScriptObject> arguments) { + Object[] parameters = new Object[] { name, new JSONArray(arguments) }; + + /* + * Must invoke manually as the RPC interface can't be used in GWT + * because of the JSONArray parameter + */ + getConnection().addMethodInvocationToQueue( + new MethodInvocation(getConnectorId(), + "com.vaadin.ui.JavaScript$JavaScriptCallbackRpc", + "call", parameters), true); + } + + @Override + public JavaScriptManagerState getState() { + return (JavaScriptManagerState) super.getState(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/extensions/javascriptmanager/JavaScriptManagerState.java b/src/com/vaadin/terminal/gwt/client/extensions/javascriptmanager/JavaScriptManagerState.java new file mode 100644 index 0000000000..fc246aff04 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/extensions/javascriptmanager/JavaScriptManagerState.java @@ -0,0 +1,22 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.extensions.javascriptmanager; + +import java.util.HashSet; +import java.util.Set; + +import com.vaadin.terminal.gwt.client.communication.SharedState; + +public class JavaScriptManagerState extends SharedState { + private Set<String> names = new HashSet<String>(); + + public Set<String> getNames() { + return names; + } + + public void setNames(Set<String> names) { + this.names = names; + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/AbstractBoxLayoutConnector.java b/src/com/vaadin/terminal/gwt/client/ui/AbstractBoxLayoutConnector.java index 854b02018f..413c605a88 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/AbstractBoxLayoutConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ui/AbstractBoxLayoutConnector.java @@ -7,10 +7,8 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; -import com.google.gwt.core.client.GWT; import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.user.client.Element; -import com.google.gwt.user.client.ui.Widget; import com.vaadin.terminal.gwt.client.AbstractFieldState; import com.vaadin.terminal.gwt.client.ComponentConnector; import com.vaadin.terminal.gwt.client.ConnectorHierarchyChangeEvent; @@ -58,11 +56,6 @@ public abstract class AbstractBoxLayoutConnector extends } @Override - protected Widget createWidget() { - return GWT.create(VBoxLayout.class); - } - - @Override public VBoxLayout getWidget() { return (VBoxLayout) super.getWidget(); } @@ -119,7 +112,6 @@ public abstract class AbstractBoxLayoutConnector extends required = ((AbstractFieldConnector) child).isRequired(); } boolean enabled = child.getState().isEnabled(); - // TODO Description is handled from somewhere else? slot.setCaption(caption, iconUrl, styles, error, showError, required, enabled); @@ -157,7 +149,7 @@ public abstract class AbstractBoxLayoutConnector extends int currentIndex = 0; VBoxLayout layout = getWidget(); - for (ComponentConnector child : getChildren()) { + for (ComponentConnector child : getChildComponents()) { Slot slot = layout.getSlot(child); if (slot.getParent() != layout) { child.addStateChangeHandler(childStateChangeHandler); @@ -218,7 +210,7 @@ public abstract class AbstractBoxLayoutConnector extends boolean equalExpandRatio = getWidget().vertical ? !isUndefinedHeight() : !isUndefinedWidth(); - for (ComponentConnector child : getChildren()) { + for (ComponentConnector child : getChildComponents()) { double expandRatio = getState().getChildData().get(child) .getExpandRatio(); if (expandRatio > 0) { @@ -227,7 +219,7 @@ public abstract class AbstractBoxLayoutConnector extends } } - for (ComponentConnector child : getChildren()) { + for (ComponentConnector child : getChildComponents()) { Slot slot = getWidget().getSlot(child); AlignmentInfo alignment = new AlignmentInfo(getState() @@ -313,7 +305,7 @@ public abstract class AbstractBoxLayoutConnector extends } private void updateAllSlotListeners() { - for (ComponentConnector child : getChildren()) { + for (ComponentConnector child : getChildComponents()) { updateSlotListeners(child); } // if (needsFixedHeight()) { @@ -553,7 +545,7 @@ public abstract class AbstractBoxLayoutConnector extends // dontListen(getWidget().getElement(), layoutResizeListener); - for (ComponentConnector child : getChildren()) { + for (ComponentConnector child : getChildComponents()) { Slot slot = getWidget().getSlot(child); if (slot.hasCaption()) { dontListen(slot.getCaptionElement(), slotCaptionResizeListener); diff --git a/src/com/vaadin/terminal/gwt/client/ui/AbstractClickEventHandler.java b/src/com/vaadin/terminal/gwt/client/ui/AbstractClickEventHandler.java index 31204aa0c6..e0ca798682 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/AbstractClickEventHandler.java +++ b/src/com/vaadin/terminal/gwt/client/ui/AbstractClickEventHandler.java @@ -3,29 +3,76 @@ */ package com.vaadin.terminal.gwt.client.ui; +import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.event.dom.client.ContextMenuEvent; import com.google.gwt.event.dom.client.ContextMenuHandler; import com.google.gwt.event.dom.client.DomEvent; import com.google.gwt.event.dom.client.DoubleClickEvent; import com.google.gwt.event.dom.client.DoubleClickHandler; +import com.google.gwt.event.dom.client.MouseDownEvent; +import com.google.gwt.event.dom.client.MouseDownHandler; import com.google.gwt.event.dom.client.MouseUpEvent; import com.google.gwt.event.dom.client.MouseUpHandler; import com.google.gwt.event.shared.EventHandler; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.Event.NativePreviewEvent; +import com.google.gwt.user.client.Event.NativePreviewHandler; import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.Util; -public abstract class AbstractClickEventHandler implements DoubleClickHandler, - ContextMenuHandler, MouseUpHandler { +public abstract class AbstractClickEventHandler implements MouseDownHandler, + MouseUpHandler, DoubleClickHandler, ContextMenuHandler { - private HandlerRegistration doubleClickHandlerRegistration; + private HandlerRegistration mouseDownHandlerRegistration; private HandlerRegistration mouseUpHandlerRegistration; + private HandlerRegistration doubleClickHandlerRegistration; private HandlerRegistration contextMenuHandlerRegistration; protected ComponentConnector connector; private String clickEventIdentifier; + /** + * The element where the last mouse down event was registered. + */ + private JavaScriptObject lastMouseDownTarget; + + /** + * Set to true by {@link #mouseUpPreviewHandler} if it gets a mouseup at the + * same element as {@link #lastMouseDownTarget}. + */ + private boolean mouseUpPreviewMatched = false; + + private HandlerRegistration mouseUpEventPreviewRegistration; + + /** + * Previews events after a mousedown to detect where the following mouseup + * hits. + */ + private final NativePreviewHandler mouseUpPreviewHandler = new NativePreviewHandler() { + + public void onPreviewNativeEvent(NativePreviewEvent event) { + if (event.getTypeInt() == Event.ONMOUSEUP) { + mouseUpEventPreviewRegistration.removeHandler(); + + // Event's reported target not always correct if event + // capture is in use + Element elementUnderMouse = Util.getElementUnderMouse(event + .getNativeEvent()); + if (lastMouseDownTarget != null + && elementUnderMouse.cast() == lastMouseDownTarget) { + mouseUpPreviewMatched = true; + } else { + System.out.println("Ignoring mouseup from " + + elementUnderMouse + " when mousedown was on " + + lastMouseDownTarget); + } + } + } + }; + public AbstractClickEventHandler(ComponentConnector connector, String clickEventIdentifier) { this.connector = connector; @@ -36,25 +83,28 @@ public abstract class AbstractClickEventHandler implements DoubleClickHandler, // Handle registering/unregistering of click handler depending on if // server side listeners have been added or removed. if (hasEventListener()) { - if (mouseUpHandlerRegistration == null) { + if (mouseDownHandlerRegistration == null) { + mouseDownHandlerRegistration = registerHandler(this, + MouseDownEvent.getType()); mouseUpHandlerRegistration = registerHandler(this, MouseUpEvent.getType()); - contextMenuHandlerRegistration = registerHandler(this, - ContextMenuEvent.getType()); doubleClickHandlerRegistration = registerHandler(this, DoubleClickEvent.getType()); + contextMenuHandlerRegistration = registerHandler(this, + ContextMenuEvent.getType()); } } else { - if (mouseUpHandlerRegistration != null) { + if (mouseDownHandlerRegistration != null) { // Remove existing handlers - doubleClickHandlerRegistration.removeHandler(); + mouseDownHandlerRegistration.removeHandler(); mouseUpHandlerRegistration.removeHandler(); + doubleClickHandlerRegistration.removeHandler(); contextMenuHandlerRegistration.removeHandler(); - contextMenuHandlerRegistration = null; + mouseDownHandlerRegistration = null; mouseUpHandlerRegistration = null; doubleClickHandlerRegistration = null; - + contextMenuHandlerRegistration = null; } } @@ -93,6 +143,7 @@ public abstract class AbstractClickEventHandler implements DoubleClickHandler, * Event handler for context menu. Prevents the browser context menu from * popping up if there is a listener for right clicks. */ + public void onContextMenu(ContextMenuEvent event) { if (hasEventListener() && shouldFireEvent(event)) { // Prevent showing the browser's context menu when there is a right @@ -101,18 +152,34 @@ public abstract class AbstractClickEventHandler implements DoubleClickHandler, } } - /** - * Event handler for mouse up. This is used to detect all single click - * events. - */ + public void onMouseDown(MouseDownEvent event) { + /* + * When getting a mousedown event, we must detect where the + * corresponding mouseup event if it's on a different part of the page. + */ + lastMouseDownTarget = event.getNativeEvent().getEventTarget(); + mouseUpPreviewMatched = false; + mouseUpEventPreviewRegistration = Event + .addNativePreviewHandler(mouseUpPreviewHandler); + } + public void onMouseUp(MouseUpEvent event) { - // TODO For perfect accuracy we should check that a mousedown has - // occured on this element before this mouseup and that no mouseup - // has occured anywhere after that. - if (hasEventListener() && shouldFireEvent(event)) { + /* + * Only fire a click if the mouseup hits the same element as the + * corresponding mousedown. This is first checked in the event preview + * but we can't fire the even there as the event might get canceled + * before it gets here. + */ + if (hasEventListener() + && mouseUpPreviewMatched + && lastMouseDownTarget != null + && Util.getElementUnderMouse(event.getNativeEvent()) == lastMouseDownTarget + && shouldFireEvent(event)) { // "Click" with left, right or middle button fireClick(event.getNativeEvent()); } + mouseUpPreviewMatched = false; + lastMouseDownTarget = null; } /** @@ -140,6 +207,7 @@ public abstract class AbstractClickEventHandler implements DoubleClickHandler, * that browsers typically fail to prevent the second click event so a * double click will result in two click events and one double click event. */ + public void onDoubleClick(DoubleClickEvent event) { if (hasEventListener() && shouldFireEvent(event)) { fireClick(event.getNativeEvent()); diff --git a/src/com/vaadin/terminal/gwt/client/ui/AbstractComponentConnector.java b/src/com/vaadin/terminal/gwt/client/ui/AbstractComponentConnector.java index 1caec0428e..a621c488be 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/AbstractComponentConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ui/AbstractComponentConnector.java @@ -14,8 +14,10 @@ import com.vaadin.terminal.gwt.client.ApplicationConnection; import com.vaadin.terminal.gwt.client.ComponentConnector; import com.vaadin.terminal.gwt.client.ComponentContainerConnector; import com.vaadin.terminal.gwt.client.ComponentState; +import com.vaadin.terminal.gwt.client.Connector; import com.vaadin.terminal.gwt.client.ConnectorMap; import com.vaadin.terminal.gwt.client.LayoutManager; +import com.vaadin.terminal.gwt.client.ServerConnector; import com.vaadin.terminal.gwt.client.TooltipInfo; import com.vaadin.terminal.gwt.client.UIDL; import com.vaadin.terminal.gwt.client.Util; @@ -26,8 +28,6 @@ import com.vaadin.terminal.gwt.client.ui.root.RootConnector; public abstract class AbstractComponentConnector extends AbstractConnector implements ComponentConnector { - private ComponentContainerConnector parent; - private Widget widget; private String lastKnownWidth = ""; @@ -77,8 +77,6 @@ public abstract class AbstractComponentConnector extends AbstractConnector @Override public void onStateChanged(StateChangeEvent stateChangeEvent) { - super.onStateChanged(stateChangeEvent); - ConnectorMap paintableMap = ConnectorMap.get(getConnection()); if (getState().getDebugId() != null) { @@ -90,7 +88,8 @@ public abstract class AbstractComponentConnector extends AbstractConnector /* * Disabled state may affect (override) tabindex so the order must be - * first setting tabindex, then enabled state. + * first setting tabindex, then enabled state (through super + * implementation). */ if (getState() instanceof TabIndexState && getWidget() instanceof Focusable) { @@ -98,7 +97,7 @@ public abstract class AbstractComponentConnector extends AbstractConnector .getTabIndex()); } - setWidgetEnabled(isEnabled()); + super.onStateChanged(stateChangeEvent); // Style names // String styleName = getStyleNames(getWidget().getStylePrimaryName()); @@ -117,10 +116,10 @@ public abstract class AbstractComponentConnector extends AbstractConnector // Set captions if (delegateCaptionHandling()) { - ComponentContainerConnector parent = getParent(); - if (parent != null) { - parent.updateCaption(this); - } else if (!(this instanceof RootConnector)) { + ServerConnector parent = getParent(); + if (parent instanceof ComponentContainerConnector) { + ((ComponentContainerConnector) parent).updateCaption(this); + } else if (parent == null && !(this instanceof RootConnector)) { VConsole.error("Parent of connector " + Util.getConnectorString(this) + " is null. This is typically an indication of a broken component hierarchy"); @@ -137,7 +136,22 @@ public abstract class AbstractComponentConnector extends AbstractConnector public void setWidgetEnabled(boolean widgetEnabled) { if (getWidget() instanceof HasEnabled) { + // set widget specific enabled state ((HasEnabled) getWidget()).setEnabled(widgetEnabled); + // add or remove v-disabled style name from the widget + getWidget().setStyleName(ApplicationConnection.DISABLED_CLASSNAME, + !widgetEnabled); + // make sure the caption has or has not v-disabled style + if (delegateCaptionHandling()) { + ServerConnector parent = getParent(); + if (parent instanceof ComponentContainerConnector) { + ((ComponentContainerConnector) parent).updateCaption(this); + } else if (parent == null && !(this instanceof RootConnector)) { + VConsole.error("Parent of connector " + + Util.getConnectorString(this) + + " is null. This is typically an indication of a broken component hierarchy"); + } + } } } @@ -148,7 +162,7 @@ public abstract class AbstractComponentConnector extends AbstractConnector // Parent should be updated if either dimension changed between relative // and non-relative if (newWidth.endsWith("%") != lastKnownWidth.endsWith("%")) { - ComponentContainerConnector parent = getParent(); + Connector parent = getParent(); if (parent instanceof ManagedLayout) { getLayoutManager().setNeedsHorizontalLayout( (ManagedLayout) parent); @@ -156,7 +170,7 @@ public abstract class AbstractComponentConnector extends AbstractConnector } if (newHeight.endsWith("%") != lastKnownHeight.endsWith("%")) { - ComponentContainerConnector parent = getParent(); + Connector parent = getParent(); if (parent instanceof ManagedLayout) { getLayoutManager().setNeedsVerticalLayout( (ManagedLayout) parent); @@ -195,23 +209,6 @@ public abstract class AbstractComponentConnector extends AbstractConnector /* * (non-Javadoc) * - * @see com.vaadin.terminal.gwt.client.Connector#isEnabled() - */ - public boolean isEnabled() { - if (!getState().isEnabled()) { - return false; - } - - if (getParent() == null) { - return true; - } else { - return getParent().isEnabled(); - } - } - - /* - * (non-Javadoc) - * * @see * com.vaadin.terminal.gwt.client.ComponentConnector#delegateCaptionHandling * () @@ -337,26 +334,6 @@ public abstract class AbstractComponentConnector extends AbstractConnector return LayoutManager.get(getConnection()); } - /* - * (non-Javadoc) - * - * @see com.vaadin.terminal.gwt.client.Connector#getParent() - */ - public ComponentContainerConnector getParent() { - return parent; - } - - /* - * (non-Javadoc) - * - * @see - * com.vaadin.terminal.gwt.client.Connector#setParent(com.vaadin.terminal - * .gwt.client.ComponentContainerConnector) - */ - public void setParent(ComponentContainerConnector parent) { - this.parent = parent; - } - /** * Checks if there is a registered server side listener for the given event * identifier. @@ -372,6 +349,13 @@ public abstract class AbstractComponentConnector extends AbstractConnector } @Override + public void updateEnabledState(boolean enabledState) { + super.updateEnabledState(enabledState); + + setWidgetEnabled(isEnabled()); + } + + @Override public void onUnregister() { super.onUnregister(); diff --git a/src/com/vaadin/terminal/gwt/client/ui/AbstractComponentContainerConnector.java b/src/com/vaadin/terminal/gwt/client/ui/AbstractComponentContainerConnector.java index 526631e4b2..c6bfba5023 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/AbstractComponentContainerConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ui/AbstractComponentContainerConnector.java @@ -3,7 +3,7 @@ */ package com.vaadin.terminal.gwt.client.ui; -import java.util.LinkedList; +import java.util.Collections; import java.util.List; import com.google.gwt.event.shared.HandlerRegistration; @@ -18,19 +18,11 @@ public abstract class AbstractComponentContainerConnector extends AbstractComponentConnector implements ComponentContainerConnector, ConnectorHierarchyChangeHandler { - List<ComponentConnector> children; + List<ComponentConnector> childComponents; private final boolean debugLogging = false; /** - * Temporary storage for last enabled state to be able to see if it has - * changed. Can be removed once we are able to listen specifically for - * enabled changes in the state. Widget.isEnabled() cannot be used as all - * Widgets do not implement HasEnabled - */ - private boolean lastWidgetEnabledState = true; - - /** * Default constructor */ public AbstractComponentContainerConnector() { @@ -43,12 +35,12 @@ public abstract class AbstractComponentContainerConnector extends * @see * com.vaadin.terminal.gwt.client.ComponentContainerConnector#getChildren() */ - public List<ComponentConnector> getChildren() { - if (children == null) { - return new LinkedList<ComponentConnector>(); + public List<ComponentConnector> getChildComponents() { + if (childComponents == null) { + return Collections.emptyList(); } - return children; + return childComponents; } /* @@ -58,8 +50,8 @@ public abstract class AbstractComponentContainerConnector extends * com.vaadin.terminal.gwt.client.ComponentContainerConnector#setChildren * (java.util.Collection) */ - public void setChildren(List<ComponentConnector> children) { - this.children = children; + public void setChildComponents(List<ComponentConnector> childComponents) { + this.childComponents = childComponents; } /* @@ -80,7 +72,7 @@ public abstract class AbstractComponentContainerConnector extends VConsole.log(oldChildren); String newChildren = "* New children: "; - for (ComponentConnector child : getChildren()) { + for (ComponentConnector child : getChildComponents()) { newChildren += Util.getConnectorString(child) + " "; } VConsole.log(newChildren); @@ -92,19 +84,4 @@ public abstract class AbstractComponentContainerConnector extends return ensureHandlerManager().addHandler( ConnectorHierarchyChangeEvent.TYPE, handler); } - - @Override - public void setWidgetEnabled(boolean widgetEnabled) { - if (lastWidgetEnabledState == widgetEnabled) { - return; - } - lastWidgetEnabledState = widgetEnabled; - - super.setWidgetEnabled(widgetEnabled); - for (ComponentConnector c : getChildren()) { - // Update children as they might be affected by the enabled state of - // their parent - c.setWidgetEnabled(c.isEnabled()); - } - } } diff --git a/src/com/vaadin/terminal/gwt/client/ui/AbstractConnector.java b/src/com/vaadin/terminal/gwt/client/ui/AbstractConnector.java index 54812a7a42..d34529ee4e 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/AbstractConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ui/AbstractConnector.java @@ -7,6 +7,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import com.google.gwt.event.shared.GwtEvent; @@ -40,6 +41,16 @@ public abstract class AbstractConnector implements ServerConnector, private final boolean debugLogging = false; private SharedState state; + private ServerConnector parent; + + /** + * Temporary storage for last enabled state to be able to see if it has + * changed. Can be removed once we are able to listen specifically for + * enabled changes in the state. Widget.isEnabled() cannot be used as all + * Widgets do not implement HasEnabled + */ + private boolean lastEnabledState = true; + private List<ServerConnector> children; /* * (non-Javadoc) @@ -137,16 +148,6 @@ public abstract class AbstractConnector implements ServerConnector, return (Collection<T>) rpcImplementations.get(rpcInterfaceId); } - /* - * (non-Javadoc) - * - * @see com.vaadin.terminal.gwt.client.Connector#isConnectorEnabled() - */ - public boolean isConnectorEnabled() { - // Client side can always receive message from the server - return true; - } - public void fireEvent(GwtEvent<?> event) { if (handlerManager != null) { handlerManager.fireEvent(event); @@ -176,6 +177,8 @@ public abstract class AbstractConnector implements ServerConnector, + Util.getConnectorString(stateChangeEvent.getConnector()) + " received by " + Util.getConnectorString(this)); } + + updateEnabledState(isEnabled()); } /* @@ -218,4 +221,47 @@ public abstract class AbstractConnector implements ServerConnector, return ConnectorStateFactory.createState(getClass()); } + public ServerConnector getParent() { + return parent; + } + + public void setParent(ServerConnector parent) { + this.parent = parent; + } + + public List<ServerConnector> getChildren() { + if (children == null) { + return Collections.emptyList(); + } + return children; + } + + public void setChildren(List<ServerConnector> children) { + this.children = children; + } + + public boolean isEnabled() { + if (!getState().isEnabled()) { + return false; + } + + if (getParent() == null) { + return true; + } else { + return getParent().isEnabled(); + } + } + + public void updateEnabledState(boolean enabledState) { + if (lastEnabledState == enabledState) { + return; + } + lastEnabledState = enabledState; + + for (ServerConnector c : getChildren()) { + // Update children as they might be affected by the enabled state of + // their parent + c.updateEnabledState(c.isEnabled()); + } + } } diff --git a/src/com/vaadin/terminal/gwt/client/ui/ClickEventHandler.java b/src/com/vaadin/terminal/gwt/client/ui/ClickEventHandler.java index cffdb1e68a..758f798ef2 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/ClickEventHandler.java +++ b/src/com/vaadin/terminal/gwt/client/ui/ClickEventHandler.java @@ -28,6 +28,7 @@ public abstract class ClickEventHandler extends AbstractClickEventHandler { * @param event * The native event that caused this click event */ + @Override protected void fireClick(NativeEvent event) { MouseEventDetails mouseDetails = MouseEventDetailsBuilder .buildMouseEventDetails(event, getRelativeToElement()); diff --git a/src/com/vaadin/terminal/gwt/client/ui/FocusableScrollPanel.java b/src/com/vaadin/terminal/gwt/client/ui/FocusableScrollPanel.java index 62697c4d98..ef1ea8521b 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/FocusableScrollPanel.java +++ b/src/com/vaadin/terminal/gwt/client/ui/FocusableScrollPanel.java @@ -34,8 +34,8 @@ public class FocusableScrollPanel extends SimpleFocusablePanel implements public FocusableScrollPanel() { // Prevent IE standard mode bug when a AbsolutePanel is contained. + TouchScrollDelegate.enableTouchScrolling(this, getElement()); Style style = getElement().getStyle(); - style.setOverflow(Overflow.AUTO); style.setProperty("zoom", "1"); style.setPosition(Position.RELATIVE); } @@ -153,7 +153,8 @@ public class FocusableScrollPanel extends SimpleFocusablePanel implements * the new vertical scroll position, in pixels */ public void setScrollPosition(int position) { - if (BrowserInfo.get().isAndroidWithBrokenScrollTop()) { + if (BrowserInfo.get().isAndroidWithBrokenScrollTop() + && BrowserInfo.get().requiresTouchScrollDelegate()) { ArrayList<com.google.gwt.dom.client.Element> elements = TouchScrollDelegate .getElements(getElement()); for (com.google.gwt.dom.client.Element el : elements) { diff --git a/src/com/vaadin/terminal/gwt/client/ui/JavaScriptComponentConnector.java b/src/com/vaadin/terminal/gwt/client/ui/JavaScriptComponentConnector.java new file mode 100644 index 0000000000..bb062a6677 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/JavaScriptComponentConnector.java @@ -0,0 +1,42 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui; + +import com.vaadin.terminal.gwt.client.JavaScriptConnectorHelper; +import com.vaadin.terminal.gwt.client.communication.HasJavaScriptConnectorHelper; +import com.vaadin.ui.AbstractJavaScriptComponent; + +@Connect(AbstractJavaScriptComponent.class) +public final class JavaScriptComponentConnector extends + AbstractComponentConnector implements HasJavaScriptConnectorHelper { + + private final JavaScriptConnectorHelper helper = new JavaScriptConnectorHelper( + this) { + @Override + protected void showInitProblem( + java.util.ArrayList<String> attemptedNames) { + getWidget().showNoInitFound(attemptedNames); + } + }; + + @Override + public JavaScriptWidget getWidget() { + return (JavaScriptWidget) super.getWidget(); + } + + @Override + protected void init() { + super.init(); + helper.init(); + } + + public JavaScriptConnectorHelper getJavascriptConnectorHelper() { + return helper; + } + + @Override + public JavaScriptComponentState getState() { + return (JavaScriptComponentState) super.getState(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/JavaScriptComponentState.java b/src/com/vaadin/terminal/gwt/client/ui/JavaScriptComponentState.java new file mode 100644 index 0000000000..6728f85ec9 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/JavaScriptComponentState.java @@ -0,0 +1,37 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import com.vaadin.terminal.gwt.client.ComponentState; +import com.vaadin.terminal.gwt.client.JavaScriptConnectorHelper.JavaScriptConnectorState; + +public class JavaScriptComponentState extends ComponentState implements + JavaScriptConnectorState { + + private Set<String> callbackNames = new HashSet<String>(); + private Map<String, Set<String>> rpcInterfaces = new HashMap<String, Set<String>>(); + + public Set<String> getCallbackNames() { + return callbackNames; + } + + public void setCallbackNames(Set<String> callbackNames) { + this.callbackNames = callbackNames; + } + + public Map<String, Set<String>> getRpcInterfaces() { + return rpcInterfaces; + } + + public void setRpcInterfaces(Map<String, Set<String>> rpcInterfaces) { + this.rpcInterfaces = rpcInterfaces; + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/JavaScriptWidget.java b/src/com/vaadin/terminal/gwt/client/ui/JavaScriptWidget.java new file mode 100644 index 0000000000..e6c3323893 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/JavaScriptWidget.java @@ -0,0 +1,25 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui; + +import java.util.ArrayList; + +import com.google.gwt.dom.client.Document; +import com.google.gwt.user.client.ui.Widget; + +public class JavaScriptWidget extends Widget { + public JavaScriptWidget() { + setElement(Document.get().createDivElement()); + } + + public void showNoInitFound(ArrayList<String> attemptedNames) { + String message = "Could not initialize JavaScriptConnector because no JavaScript init function was found. Make sure one of these functions are defined: <ul>"; + for (String name : attemptedNames) { + message += "<li>" + name + "</li>"; + } + message += "</ul>"; + + getElement().setInnerHTML(message); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/TouchScrollDelegate.java b/src/com/vaadin/terminal/gwt/client/ui/TouchScrollDelegate.java index 8b2248aff6..7302f9f2ac 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/TouchScrollDelegate.java +++ b/src/com/vaadin/terminal/gwt/client/ui/TouchScrollDelegate.java @@ -4,6 +4,8 @@ package com.vaadin.terminal.gwt.client.ui; import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; import com.google.gwt.animation.client.Animation; import com.google.gwt.core.client.Duration; @@ -15,10 +17,12 @@ import com.google.gwt.dom.client.Style; import com.google.gwt.dom.client.Touch; import com.google.gwt.event.dom.client.ScrollHandler; import com.google.gwt.event.dom.client.TouchStartEvent; +import com.google.gwt.event.dom.client.TouchStartHandler; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.Event.NativePreviewEvent; import com.google.gwt.user.client.Event.NativePreviewHandler; +import com.google.gwt.user.client.ui.Widget; import com.vaadin.terminal.gwt.client.BrowserInfo; import com.vaadin.terminal.gwt.client.VConsole; @@ -68,7 +72,7 @@ public class TouchScrollDelegate implements NativePreviewHandler { private static final double DECELERATION = 0.002; private static final int MAX_DURATION = 1500; private int origY; - private Element[] scrollableElements; + private HashSet<Element> scrollableElements; private Element scrolledElement; private int origScrollTop; private HandlerRegistration handlerRegistration; @@ -86,8 +90,107 @@ public class TouchScrollDelegate implements NativePreviewHandler { private static final boolean androidWithBrokenScrollTop = BrowserInfo.get() .isAndroidWithBrokenScrollTop(); + /** + * A helper class for making a widget scrollable. Uses native scrolling if + * supported by the browser, otherwise registers a touch start handler + * delegating to a TouchScrollDelegate instance. + */ + public static class TouchScrollHandler implements TouchStartHandler { + + private static final String SCROLLABLE_CLASSNAME = "v-scrollable"; + + private final TouchScrollDelegate delegate; + private final boolean requiresDelegate = BrowserInfo.get() + .requiresTouchScrollDelegate(); + + /** + * Constructs a scroll handler for the given widget. + * + * @param widget + * The widget that contains scrollable elements + * @param scrollables + * The elements of the widget that should be scrollable. + */ + public TouchScrollHandler(Widget widget, Element... scrollables) { + if (requiresDelegate) { + delegate = new TouchScrollDelegate(); + widget.addDomHandler(this, TouchStartEvent.getType()); + } else { + delegate = null; + } + setElements(scrollables); + } + + public void onTouchStart(TouchStartEvent event) { + assert delegate != null; + delegate.onTouchStart(event); + } + + public void debug(Element e) { + VConsole.log("Classes: " + e.getClassName() + " overflow: " + + e.getStyle().getProperty("overflow") + " w-o-s: " + + e.getStyle().getProperty("WebkitOverflowScrolling")); + } + + /** + * Registers the given element as scrollable. + */ + public void addElement(Element scrollable) { + scrollable.addClassName(SCROLLABLE_CLASSNAME); + if (requiresDelegate) { + delegate.scrollableElements.add(scrollable); + } + } + + /** + * Unregisters the given element as scrollable. Should be called when a + * previously-registered element is removed from the DOM to prevent + * memory leaks. + */ + public void removeElement(Element scrollable) { + scrollable.removeClassName(SCROLLABLE_CLASSNAME); + if (requiresDelegate) { + delegate.scrollableElements.remove(scrollable); + } + } + + /** + * Registers the given elements as scrollable, removing previously + * registered scrollables from this handler. + * + * @param scrollables + * The elements that should be scrollable + */ + public void setElements(Element... scrollables) { + if (requiresDelegate) { + for (Element e : delegate.scrollableElements) { + e.removeClassName(SCROLLABLE_CLASSNAME); + } + delegate.scrollableElements.clear(); + } + for (Element e : scrollables) { + addElement(e); + } + } + } + + /** + * Makes the given elements scrollable, either natively or by using a + * TouchScrollDelegate, depending on platform capabilities. + * + * @param widget + * The widget that contains scrollable elements + * @param scrollables + * The elements inside the widget that should be scrollable + * @return A scroll handler for the given widget. + */ + public static TouchScrollHandler enableTouchScrolling(Widget widget, + Element... scrollables) { + return new TouchScrollHandler(widget, scrollables); + } + public TouchScrollDelegate(Element... elements) { - scrollableElements = elements; + setElements(elements); } public void setScrollHandler(ScrollHandler scrollHandler) { @@ -535,8 +638,8 @@ public class TouchScrollDelegate implements NativePreviewHandler { } } - public void setElements(com.google.gwt.user.client.Element[] elements) { - scrollableElements = elements; + public void setElements(Element[] elements) { + scrollableElements = new HashSet<Element>(Arrays.asList(elements)); } /** diff --git a/src/com/vaadin/terminal/gwt/client/ui/absolutelayout/AbsoluteLayoutConnector.java b/src/com/vaadin/terminal/gwt/client/ui/absolutelayout/AbsoluteLayoutConnector.java index b42c0acea7..91436f5353 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/absolutelayout/AbsoluteLayoutConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ui/absolutelayout/AbsoluteLayoutConnector.java @@ -109,7 +109,7 @@ public class AbsoluteLayoutConnector extends // TODO Margin handling - for (ComponentConnector child : getChildren()) { + for (ComponentConnector child : getChildComponents()) { getWrapper(child).setPosition( getState().getConnectorPosition(child)); } @@ -133,7 +133,7 @@ public class AbsoluteLayoutConnector extends public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) { super.onConnectorHierarchyChange(event); - for (ComponentConnector child : getChildren()) { + for (ComponentConnector child : getChildComponents()) { getWrapper(child); } @@ -149,7 +149,7 @@ public class AbsoluteLayoutConnector extends public void layoutVertically() { VAbsoluteLayout layout = getWidget(); - for (ComponentConnector paintable : getChildren()) { + for (ComponentConnector paintable : getChildComponents()) { Widget widget = paintable.getWidget(); AbsoluteWrapper wrapper = (AbsoluteWrapper) widget.getParent(); Style wrapperStyle = wrapper.getElement().getStyle(); @@ -181,7 +181,7 @@ public class AbsoluteLayoutConnector extends public void layoutHorizontally() { VAbsoluteLayout layout = getWidget(); - for (ComponentConnector paintable : getChildren()) { + for (ComponentConnector paintable : getChildComponents()) { AbsoluteWrapper wrapper = getWrapper(paintable); Style wrapperStyle = wrapper.getElement().getStyle(); diff --git a/src/com/vaadin/terminal/gwt/client/ui/accordion/VAccordion.java b/src/com/vaadin/terminal/gwt/client/ui/accordion/VAccordion.java index dcd520bbb3..b83d5afb00 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/accordion/VAccordion.java +++ b/src/com/vaadin/terminal/gwt/client/ui/accordion/VAccordion.java @@ -20,6 +20,8 @@ import com.vaadin.terminal.gwt.client.ConnectorMap; import com.vaadin.terminal.gwt.client.UIDL; import com.vaadin.terminal.gwt.client.Util; import com.vaadin.terminal.gwt.client.VCaption; +import com.vaadin.terminal.gwt.client.ui.TouchScrollDelegate; +import com.vaadin.terminal.gwt.client.ui.TouchScrollDelegate.TouchScrollHandler; import com.vaadin.terminal.gwt.client.ui.tabsheet.TabsheetBaseConnector; import com.vaadin.terminal.gwt.client.ui.tabsheet.VTabsheetBase; @@ -35,8 +37,11 @@ public class VAccordion extends VTabsheetBase { int selectedUIDLItemIndex = -1; + private final TouchScrollHandler touchScrollHandler; + public VAccordion() { super(CLASSNAME); + touchScrollHandler = TouchScrollDelegate.enableTouchScrolling(this); } @Override @@ -145,7 +150,6 @@ public class VAccordion extends VTabsheetBase { } } } - if (!alreadyOpen) { item.open(); activeTabIndex = itemIndex; @@ -342,11 +346,13 @@ public class VAccordion extends VTabsheetBase { DOM.appendChild(captionNode, caption.getElement()); DOM.appendChild(getElement(), captionNode); DOM.appendChild(getElement(), content); - setStyleName(CLASSNAME + "-item"); - DOM.setElementProperty(content, "className", CLASSNAME - + "-item-content"); - DOM.setElementProperty(captionNode, "className", CLASSNAME - + "-item-caption"); + + getElement().addClassName(CLASSNAME + "-item"); + captionNode.addClassName(CLASSNAME + "-item-caption"); + content.addClassName(CLASSNAME + "-item-content"); + + touchScrollHandler.addElement(getContainerElement()); + close(); } @@ -488,6 +494,7 @@ public class VAccordion extends VTabsheetBase { protected void removeTab(int index) { StackItem item = getStackItem(index); remove(item); + touchScrollHandler.removeElement(item.getContainerElement()); } @Override diff --git a/src/com/vaadin/terminal/gwt/client/ui/button/ButtonState.java b/src/com/vaadin/terminal/gwt/client/ui/button/ButtonState.java index fdc053b3ae..2daceea0e8 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/button/ButtonState.java +++ b/src/com/vaadin/terminal/gwt/client/ui/button/ButtonState.java @@ -5,6 +5,7 @@ package com.vaadin.terminal.gwt.client.ui.button; import com.vaadin.terminal.gwt.client.ComponentState; +import com.vaadin.terminal.gwt.client.ui.TabIndexState; import com.vaadin.ui.Button; /** @@ -14,10 +15,14 @@ import com.vaadin.ui.Button; * * @since 7.0 */ -public class ButtonState extends ComponentState { +public class ButtonState extends ComponentState implements TabIndexState { private boolean disableOnClick = false; private int clickShortcutKeyCode = 0; /** + * The tab order number of this field. + */ + private int tabIndex = 0; + /** * If caption should be rendered in HTML */ private boolean htmlContentAllowed = false; @@ -92,4 +97,22 @@ public class ButtonState extends ComponentState { return htmlContentAllowed; } + /* + * (non-Javadoc) + * + * @see com.vaadin.terminal.gwt.client.ui.TabIndexState#getTabIndex() + */ + public int getTabIndex() { + return tabIndex; + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.terminal.gwt.client.ui.TabIndexState#setTabIndex(int) + */ + public void setTabIndex(int tabIndex) { + this.tabIndex = tabIndex; + } + } diff --git a/src/com/vaadin/terminal/gwt/client/ui/button/VButton.java b/src/com/vaadin/terminal/gwt/client/ui/button/VButton.java index e5e7dbba8b..0cd8bc54f4 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/button/VButton.java +++ b/src/com/vaadin/terminal/gwt/client/ui/button/VButton.java @@ -10,6 +10,7 @@ import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.dom.client.KeyCodes; +import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.ui.Accessibility; @@ -61,7 +62,9 @@ public class VButton extends FocusWidget implements ClickHandler { private boolean isCapturing; /** - * If <code>true</code>, this widget has focus with the space bar down. + * If <code>true</code>, this widget has focus with the space bar down. This + * means that we will get events when the button is released, but we should + * trigger the button only if the button is still focused at that point. */ private boolean isFocusing; @@ -74,6 +77,14 @@ public class VButton extends FocusWidget implements ClickHandler { protected int clickShortcut = 0; + private HandlerRegistration focusHandlerRegistration; + private HandlerRegistration blurHandlerRegistration; + + /** + * If caption should be rendered in HTML + */ + protected boolean htmlCaption = false; + public VButton() { super(DOM.createDiv()); setTabIndex(0); @@ -229,37 +240,28 @@ public class VButton extends FocusWidget implements ClickHandler { if ((event.getTypeInt() & Event.KEYEVENTS) != 0) { switch (type) { case Event.ONKEYDOWN: + // Stop propagation when the user starts pressing a button that + // we are handling to prevent actions from getting triggered if (event.getKeyCode() == 32 /* space */) { isFocusing = true; event.preventDefault(); + event.stopPropagation(); + } else if (event.getKeyCode() == KeyCodes.KEY_ENTER) { + event.stopPropagation(); } break; case Event.ONKEYUP: if (isFocusing && event.getKeyCode() == 32 /* space */) { isFocusing = false; - - /* - * If click shortcut is space then the shortcut handler will - * take care of the click. - */ - if (clickShortcut != 32 /* space */) { - onClick(); - } - + onClick(); + event.stopPropagation(); event.preventDefault(); } break; case Event.ONKEYPRESS: if (event.getKeyCode() == KeyCodes.KEY_ENTER) { - - /* - * If click shortcut is enter then the shortcut handler will - * take care of the click. - */ - if (clickShortcut != KeyCodes.KEY_ENTER) { - onClick(); - } - + onClick(); + event.stopPropagation(); event.preventDefault(); } break; @@ -336,12 +338,10 @@ public class VButton extends FocusWidget implements ClickHandler { Accessibility.removeState(getElement(), Accessibility.STATE_PRESSED); super.setTabIndex(-1); - addStyleName(ApplicationConnection.DISABLED_CLASSNAME); } else { Accessibility.setState(getElement(), Accessibility.STATE_PRESSED, "false"); super.setTabIndex(tabIndex); - removeStyleName(ApplicationConnection.DISABLED_CLASSNAME); } } } diff --git a/src/com/vaadin/terminal/gwt/client/ui/combobox/VFilterSelect.java b/src/com/vaadin/terminal/gwt/client/ui/combobox/VFilterSelect.java index d29eda0d6a..8c5d521445 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/combobox/VFilterSelect.java +++ b/src/com/vaadin/terminal/gwt/client/ui/combobox/VFilterSelect.java @@ -69,7 +69,8 @@ import com.vaadin.terminal.gwt.client.ui.menubar.MenuItem; */ @SuppressWarnings("deprecation") public class VFilterSelect extends Composite implements Field, KeyDownHandler, - KeyUpHandler, ClickHandler, FocusHandler, BlurHandler, Focusable { + KeyUpHandler, ClickHandler, FocusHandler, BlurHandler, Focusable, + SubPartAware { /** * Represents a suggestion in the suggestion popup box @@ -100,6 +101,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, * contains an image tag with the rows icon (if an icon has been * specified) and the caption of the item */ + public String getDisplayString() { final StringBuffer sb = new StringBuffer(); if (iconUri != null) { @@ -122,6 +124,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, /** * Get a string that represents this item. This is used in the text box. */ + public String getReplacementString() { return caption; } @@ -147,6 +150,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, /** * Executes a selection of this item. */ + public void execute() { onSuggestionSelected(this); } @@ -392,6 +396,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, * com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt * .user.client.Event) */ + @Override public void onBrowserEvent(Event event) { if (event.getTypeInt() == Event.ONCLICK) { @@ -449,6 +454,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, * com.google.gwt.user.client.ui.PopupPanel$PositionCallback#setPosition * (int, int) */ + public void setPosition(int offsetWidth, int offsetHeight) { int top = -1; @@ -541,6 +547,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, * com.google.gwt.event.logical.shared.CloseHandler#onClose(com.google * .gwt.event.logical.shared.CloseEvent) */ + @Override public void onClose(CloseEvent<PopupPanel> event) { if (event.isAutoClosed()) { @@ -824,6 +831,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, * com.google.gwt.user.client.ui.TextBoxBase#onBrowserEvent(com.google * .gwt.user.client.Event) */ + @Override public void onBrowserEvent(Event event) { super.onBrowserEvent(event); @@ -832,8 +840,8 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, } } - @Override // Overridden to avoid selecting text when text input is disabled + @Override public void setSelectionRange(int pos, int length) { if (textInputEnabled) { super.setSelectionRange(pos, length); @@ -857,6 +865,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, * com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt * .user.client.Event) */ + @Override public void onBrowserEvent(Event event) { super.onBrowserEvent(event); @@ -951,6 +960,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, public VFilterSelect() { selectedItemIcon.setStyleName("v-icon"); selectedItemIcon.addLoadHandler(new LoadHandler() { + public void onLoad(LoadEvent event) { if (BrowserInfo.get().isIE8()) { // IE8 needs some help to discover it should reposition the @@ -1203,6 +1213,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, * com.google.gwt.event.dom.client.KeyDownHandler#onKeyDown(com.google.gwt * .event.dom.client.KeyDownEvent) */ + public void onKeyDown(KeyDownEvent event) { if (enabled && !readonly) { int keyCode = event.getNativeKeyCode(); @@ -1364,6 +1375,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, * @param event * The KeyUpEvent of the key depressed */ + public void onKeyUp(KeyUpEvent event) { if (enabled && !readonly) { switch (event.getNativeKeyCode()) { @@ -1411,6 +1423,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, /** * Listener for popupopener */ + public void onClick(ClickEvent event) { if (textInputEnabled && event.getNativeEvent().getEventTarget().cast() == tb @@ -1474,6 +1487,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event * .dom.client.FocusEvent) */ + public void onFocus(FocusEvent event) { /* @@ -1567,6 +1581,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, * * @see com.vaadin.terminal.gwt.client.Focusable#focus() */ + public void focus() { focused = true; if (prompting && !readonly) { @@ -1674,4 +1689,22 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, super.onDetach(); suggestionPopup.hide(); } + + public Element getSubPartElement(String subPart) { + if ("textbox".equals(subPart)) { + return this.tb.getElement(); + } else if ("button".equals(subPart)) { + return this.popupOpener.getElement(); + } + return null; + } + + public String getSubPartName(Element subElement) { + if (tb.getElement().isOrHasChild(subElement)) { + return "textbox"; + } else if (popupOpener.getElement().isOrHasChild(subElement)) { + return "button"; + } + return null; + } } diff --git a/src/com/vaadin/terminal/gwt/client/ui/csslayout/CssLayoutConnector.java b/src/com/vaadin/terminal/gwt/client/ui/csslayout/CssLayoutConnector.java index 71d670fe9d..4d341bddfc 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/csslayout/CssLayoutConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ui/csslayout/CssLayoutConnector.java @@ -64,7 +64,7 @@ public class CssLayoutConnector extends AbstractLayoutConnector { getWidget().setMarginStyles( new VMarginInfo(getState().getMarginsBitmask())); - for (ComponentConnector child : getChildren()) { + for (ComponentConnector child : getChildComponents()) { if (!getState().getChildCss().containsKey(child)) { continue; } @@ -92,7 +92,7 @@ public class CssLayoutConnector extends AbstractLayoutConnector { int index = 0; FlowPane cssLayoutWidgetContainer = getWidget().panel; - for (ComponentConnector child : getChildren()) { + for (ComponentConnector child : getChildComponents()) { VCaption childCaption = childToCaption.get(child); if (childCaption != null) { cssLayoutWidgetContainer.addOrMove(childCaption, index++); diff --git a/src/com/vaadin/terminal/gwt/client/ui/customcomponent/CustomComponentConnector.java b/src/com/vaadin/terminal/gwt/client/ui/customcomponent/CustomComponentConnector.java index 981818fc69..5001711d6c 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/customcomponent/CustomComponentConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ui/customcomponent/CustomComponentConnector.java @@ -28,8 +28,8 @@ public class CustomComponentConnector extends super.onConnectorHierarchyChange(event); ComponentConnector newChild = null; - if (getChildren().size() == 1) { - newChild = getChildren().get(0); + if (getChildComponents().size() == 1) { + newChild = getChildComponents().get(0); } VCustomComponent customComponent = getWidget(); diff --git a/src/com/vaadin/terminal/gwt/client/ui/customlayout/CustomLayoutConnector.java b/src/com/vaadin/terminal/gwt/client/ui/customlayout/CustomLayoutConnector.java index ca24cfc91a..f8861caf92 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/customlayout/CustomLayoutConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ui/customlayout/CustomLayoutConnector.java @@ -75,7 +75,7 @@ public class CustomLayoutConnector extends AbstractLayoutConnector implements updateHtmlTemplate(); // For all contained widgets - for (ComponentConnector child : getChildren()) { + for (ComponentConnector child : getChildComponents()) { String location = getState().getChildLocations().get(child); try { getWidget().setWidget(child.getWidget(), location); diff --git a/src/com/vaadin/terminal/gwt/client/ui/dd/AcceptCriterion.java b/src/com/vaadin/terminal/gwt/client/ui/dd/AcceptCriterion.java new file mode 100644 index 0000000000..8a026e4d2e --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/AcceptCriterion.java @@ -0,0 +1,33 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.dd; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An annotation type used to point the server side counterpart for client side + * a {@link VAcceptCriterion} class. + * <p> + * Annotations are used at GWT compilation phase, so remember to rebuild your + * widgetset if you do changes for {@link AcceptCriterion} mappings. + * + * Prior to Vaadin 7, the mapping was done with an annotation on server side + * classes. + * + * @since 7.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface AcceptCriterion { + /** + * @return the class of the server side counterpart for the annotated + * criterion + */ + Class<?> value(); + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/dd/DDUtil.java b/src/com/vaadin/terminal/gwt/client/ui/dd/DDUtil.java index 02c1fe5061..97f5eb86fd 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/dd/DDUtil.java +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/DDUtil.java @@ -5,6 +5,7 @@ package com.vaadin.terminal.gwt.client.ui.dd; import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.Window; import com.vaadin.terminal.gwt.client.Util; public class DDUtil { @@ -43,8 +44,11 @@ public class DDUtil { public static VerticalDropLocation getVerticalDropLocation(Element element, int offsetHeight, int clientY, double topBottomRatio) { - int absoluteTop = element.getAbsoluteTop(); - int fromTop = clientY - absoluteTop; + // Event coordinates are relative to the viewport, element absolute + // position is relative to the document. Make element position relative + // to viewport by adjusting for viewport scrolling. See #6021 + int elementTop = element.getAbsoluteTop() - Window.getScrollTop(); + int fromTop = clientY - elementTop; float percentageFromTop = (fromTop / (float) offsetHeight); if (percentageFromTop < topBottomRatio) { @@ -74,14 +78,17 @@ public class DDUtil { public static HorizontalDropLocation getHorizontalDropLocation( Element element, int clientX, double leftRightRatio) { - int absoluteLeft = element.getAbsoluteLeft(); + // Event coordinates are relative to the viewport, element absolute + // position is relative to the document. Make element position relative + // to viewport by adjusting for viewport scrolling. See #6021 + int elementLeft = element.getAbsoluteLeft() - Window.getScrollLeft(); int offsetWidth = element.getOffsetWidth(); - int fromTop = clientX - absoluteLeft; + int fromLeft = clientX - elementLeft; - float percentageFromTop = (fromTop / (float) offsetWidth); - if (percentageFromTop < leftRightRatio) { + float percentageFromLeft = (fromLeft / (float) offsetWidth); + if (percentageFromLeft < leftRightRatio) { return HorizontalDropLocation.LEFT; - } else if (percentageFromTop > 1 - leftRightRatio) { + } else if (percentageFromLeft > 1 - leftRightRatio) { return HorizontalDropLocation.RIGHT; } else { return HorizontalDropLocation.CENTER; diff --git a/src/com/vaadin/terminal/gwt/client/ui/dd/VAcceptAll.java b/src/com/vaadin/terminal/gwt/client/ui/dd/VAcceptAll.java index 51782c002d..a864a93c2a 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/dd/VAcceptAll.java +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/VAcceptAll.java @@ -6,8 +6,10 @@ */ package com.vaadin.terminal.gwt.client.ui.dd; +import com.vaadin.event.dd.acceptcriteria.AcceptAll; import com.vaadin.terminal.gwt.client.UIDL; +@AcceptCriterion(AcceptAll.class) final public class VAcceptAll extends VAcceptCriterion { @Override diff --git a/src/com/vaadin/terminal/gwt/client/ui/dd/VAnd.java b/src/com/vaadin/terminal/gwt/client/ui/dd/VAnd.java index 65c67e6b8a..19399d7d4a 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/dd/VAnd.java +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/VAnd.java @@ -6,8 +6,10 @@ */ package com.vaadin.terminal.gwt.client.ui.dd; +import com.vaadin.event.dd.acceptcriteria.And; import com.vaadin.terminal.gwt.client.UIDL; +@AcceptCriterion(And.class) final public class VAnd extends VAcceptCriterion implements VAcceptCallback { private boolean b1; diff --git a/src/com/vaadin/terminal/gwt/client/ui/dd/VContainsDataFlavor.java b/src/com/vaadin/terminal/gwt/client/ui/dd/VContainsDataFlavor.java index 4a95034d4a..3cd341eefd 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/dd/VContainsDataFlavor.java +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/VContainsDataFlavor.java @@ -6,8 +6,10 @@ */ package com.vaadin.terminal.gwt.client.ui.dd; +import com.vaadin.event.dd.acceptcriteria.ContainsDataFlavor; import com.vaadin.terminal.gwt.client.UIDL; +@AcceptCriterion(ContainsDataFlavor.class) final public class VContainsDataFlavor extends VAcceptCriterion { @Override diff --git a/src/com/vaadin/terminal/gwt/client/ui/dd/VDragSourceIs.java b/src/com/vaadin/terminal/gwt/client/ui/dd/VDragSourceIs.java index aabbf58b24..b6af81085f 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/dd/VDragSourceIs.java +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/VDragSourceIs.java @@ -3,6 +3,7 @@ */ package com.vaadin.terminal.gwt.client.ui.dd; +import com.vaadin.event.dd.acceptcriteria.SourceIs; import com.vaadin.terminal.gwt.client.ComponentConnector; import com.vaadin.terminal.gwt.client.ConnectorMap; import com.vaadin.terminal.gwt.client.UIDL; @@ -12,6 +13,7 @@ import com.vaadin.terminal.gwt.client.UIDL; * * @since 6.3 */ +@AcceptCriterion(SourceIs.class) final public class VDragSourceIs extends VAcceptCriterion { @Override diff --git a/src/com/vaadin/terminal/gwt/client/ui/dd/VIsOverId.java b/src/com/vaadin/terminal/gwt/client/ui/dd/VIsOverId.java index 90e2b033c9..5dad4873ea 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/dd/VIsOverId.java +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/VIsOverId.java @@ -9,7 +9,9 @@ package com.vaadin.terminal.gwt.client.ui.dd; import com.vaadin.terminal.gwt.client.ComponentConnector; import com.vaadin.terminal.gwt.client.ConnectorMap; import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.ui.AbstractSelect; +@AcceptCriterion(AbstractSelect.TargetItemIs.class) final public class VIsOverId extends VAcceptCriterion { @Override diff --git a/src/com/vaadin/terminal/gwt/client/ui/dd/VItemIdIs.java b/src/com/vaadin/terminal/gwt/client/ui/dd/VItemIdIs.java index eb55c1a91c..ca4d0e900d 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/dd/VItemIdIs.java +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/VItemIdIs.java @@ -8,7 +8,9 @@ package com.vaadin.terminal.gwt.client.ui.dd; import com.vaadin.terminal.gwt.client.ComponentConnector; import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.ui.AbstractSelect; +@AcceptCriterion(AbstractSelect.AcceptItem.class) final public class VItemIdIs extends VAcceptCriterion { @Override diff --git a/src/com/vaadin/terminal/gwt/client/ui/dd/VLazyInitItemIdentifiers.java b/src/com/vaadin/terminal/gwt/client/ui/dd/VLazyInitItemIdentifiers.java index b824c1cb65..e3bed02642 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/dd/VLazyInitItemIdentifiers.java +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/VLazyInitItemIdentifiers.java @@ -9,15 +9,29 @@ package com.vaadin.terminal.gwt.client.ui.dd; import java.util.HashSet; import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.ui.Table; +import com.vaadin.ui.Tree; /** * */ -final public class VLazyInitItemIdentifiers extends VAcceptCriterion { +public class VLazyInitItemIdentifiers extends VAcceptCriterion { private boolean loaded = false; private HashSet<String> hashSet; private VDragEvent lastDragEvent; + @AcceptCriterion(Table.TableDropCriterion.class) + final public static class VTableLazyInitItemIdentifiers extends + VLazyInitItemIdentifiers { + // all logic in superclass + } + + @AcceptCriterion(Tree.TreeDropCriterion.class) + final public static class VTreeLazyInitItemIdentifiers extends + VLazyInitItemIdentifiers { + // all logic in superclass + } + @Override public void accept(final VDragEvent drag, UIDL configuration, final VAcceptCallback callback) { diff --git a/src/com/vaadin/terminal/gwt/client/ui/dd/VNot.java b/src/com/vaadin/terminal/gwt/client/ui/dd/VNot.java index f4ba868497..e4d2dff606 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/dd/VNot.java +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/VNot.java @@ -6,6 +6,7 @@ */ package com.vaadin.terminal.gwt.client.ui.dd; +import com.vaadin.event.dd.acceptcriteria.Not; import com.vaadin.terminal.gwt.client.UIDL; import com.vaadin.terminal.gwt.client.VConsole; @@ -13,6 +14,7 @@ import com.vaadin.terminal.gwt.client.VConsole; * TODO implementation could now be simplified/optimized * */ +@AcceptCriterion(Not.class) final public class VNot extends VAcceptCriterion { private boolean b1; private VAcceptCriterion crit1; diff --git a/src/com/vaadin/terminal/gwt/client/ui/dd/VOr.java b/src/com/vaadin/terminal/gwt/client/ui/dd/VOr.java index e47447b7fc..91ba4bf0c4 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/dd/VOr.java +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/VOr.java @@ -6,11 +6,13 @@ */ package com.vaadin.terminal.gwt.client.ui.dd; +import com.vaadin.event.dd.acceptcriteria.Or; import com.vaadin.terminal.gwt.client.UIDL; /** * */ +@AcceptCriterion(Or.class) final public class VOr extends VAcceptCriterion implements VAcceptCallback { private boolean accepted; diff --git a/src/com/vaadin/terminal/gwt/client/ui/dd/VServerAccept.java b/src/com/vaadin/terminal/gwt/client/ui/dd/VServerAccept.java index bccb6656aa..64c2da5320 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/dd/VServerAccept.java +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/VServerAccept.java @@ -6,8 +6,10 @@ */ package com.vaadin.terminal.gwt.client.ui.dd; +import com.vaadin.event.dd.acceptcriteria.ServerSideCriterion; import com.vaadin.terminal.gwt.client.UIDL; +@AcceptCriterion(ServerSideCriterion.class) final public class VServerAccept extends VAcceptCriterion { @Override public void accept(final VDragEvent drag, UIDL configuration, diff --git a/src/com/vaadin/terminal/gwt/client/ui/dd/VSourceIsTarget.java b/src/com/vaadin/terminal/gwt/client/ui/dd/VSourceIsTarget.java index 430b422b34..2365eabe22 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/dd/VSourceIsTarget.java +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/VSourceIsTarget.java @@ -6,9 +6,11 @@ */ package com.vaadin.terminal.gwt.client.ui.dd; +import com.vaadin.event.dd.acceptcriteria.SourceIsTarget; import com.vaadin.terminal.gwt.client.ComponentConnector; import com.vaadin.terminal.gwt.client.UIDL; +@AcceptCriterion(SourceIsTarget.class) final public class VSourceIsTarget extends VAcceptCriterion { @Override diff --git a/src/com/vaadin/terminal/gwt/client/ui/dd/VTargetDetailIs.java b/src/com/vaadin/terminal/gwt/client/ui/dd/VTargetDetailIs.java index 713a0d6646..610d555745 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/dd/VTargetDetailIs.java +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/VTargetDetailIs.java @@ -6,8 +6,10 @@ */ package com.vaadin.terminal.gwt.client.ui.dd; +import com.vaadin.event.dd.acceptcriteria.TargetDetailIs; import com.vaadin.terminal.gwt.client.UIDL; +@AcceptCriterion(TargetDetailIs.class) final public class VTargetDetailIs extends VAcceptCriterion { @Override diff --git a/src/com/vaadin/terminal/gwt/client/ui/dd/VTargetInSubtree.java b/src/com/vaadin/terminal/gwt/client/ui/dd/VTargetInSubtree.java index f69fa85290..56478b2b95 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/dd/VTargetInSubtree.java +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/VTargetInSubtree.java @@ -10,7 +10,9 @@ import com.google.gwt.user.client.ui.Widget; import com.vaadin.terminal.gwt.client.UIDL; import com.vaadin.terminal.gwt.client.ui.tree.VTree; import com.vaadin.terminal.gwt.client.ui.tree.VTree.TreeNode; +import com.vaadin.ui.Tree; +@AcceptCriterion(Tree.TargetInSubtree.class) final public class VTargetInSubtree extends VAcceptCriterion { @Override diff --git a/src/com/vaadin/terminal/gwt/client/ui/draganddropwrapper/DragAndDropWrapperConnector.java b/src/com/vaadin/terminal/gwt/client/ui/draganddropwrapper/DragAndDropWrapperConnector.java index 57f6835bcc..2a78cc4433 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/draganddropwrapper/DragAndDropWrapperConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ui/draganddropwrapper/DragAndDropWrapperConnector.java @@ -57,6 +57,9 @@ public class DragAndDropWrapperConnector extends CustomComponentConnector getWidget().initDragStartMode(); getWidget().html5DataFlavors = uidl .getMapAttribute(VDragAndDropWrapper.HTML5_DATA_FLAVORS); + + // Used to prevent wrapper from stealing tooltips when not defined + getWidget().hasTooltip = getState().hasDescription(); } } diff --git a/src/com/vaadin/terminal/gwt/client/ui/draganddropwrapper/VDragAndDropWrapper.java b/src/com/vaadin/terminal/gwt/client/ui/draganddropwrapper/VDragAndDropWrapper.java index d09b81e1e1..4c36e92bbb 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/draganddropwrapper/VDragAndDropWrapper.java +++ b/src/com/vaadin/terminal/gwt/client/ui/draganddropwrapper/VDragAndDropWrapper.java @@ -60,6 +60,8 @@ public class VDragAndDropWrapper extends VCustomComponent implements private static final String CLASSNAME = "v-ddwrapper"; protected static final String DRAGGABLE = "draggable"; + boolean hasTooltip = false; + public VDragAndDropWrapper() { super(); sinkEvents(VTooltip.TOOLTIP_EVENTS); @@ -67,6 +69,7 @@ public class VDragAndDropWrapper extends VCustomComponent implements hookHtml5Events(getElement()); setStyleName(CLASSNAME); addDomHandler(new MouseDownHandler() { + public void onMouseDown(MouseDownEvent event) { if (startDrag(event.getNativeEvent())) { event.preventDefault(); // prevent text selection @@ -75,6 +78,7 @@ public class VDragAndDropWrapper extends VCustomComponent implements }, MouseDownEvent.getType()); addDomHandler(new TouchStartHandler() { + public void onTouchStart(TouchStartEvent event) { if (startDrag(event.getNativeEvent())) { /* @@ -92,7 +96,8 @@ public class VDragAndDropWrapper extends VCustomComponent implements public void onBrowserEvent(Event event) { super.onBrowserEvent(event); - if (client != null) { + if (hasTooltip && client != null) { + // Override child tooltips if the wrapper has a tooltip defined client.handleTooltipEvent(event, this); } } @@ -172,6 +177,7 @@ public class VDragAndDropWrapper extends VCustomComponent implements private boolean uploading; private ReadyStateChangeHandler readyStateChangeHandler = new ReadyStateChangeHandler() { + public void onReadyStateChange(XMLHttpRequest xhr) { if (xhr.getReadyState() == XMLHttpRequest.DONE) { // visit server for possible @@ -269,6 +275,7 @@ public class VDragAndDropWrapper extends VCustomComponent implements try { dragleavetimer = new Timer() { + @Override public void run() { // Yes, dragleave happens before drop. Makes no sense to me. @@ -455,6 +462,7 @@ public class VDragAndDropWrapper extends VCustomComponent implements if (detailsChanged) { currentlyValid = false; validate(new VAcceptCallback() { + public void accepted(VDragEvent event) { dragAccepted(drag); } diff --git a/src/com/vaadin/terminal/gwt/client/ui/form/VForm.java b/src/com/vaadin/terminal/gwt/client/ui/form/VForm.java index e3a0c9b321..81f24a8e7e 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/form/VForm.java +++ b/src/com/vaadin/terminal/gwt/client/ui/form/VForm.java @@ -4,7 +4,6 @@ package com.vaadin.terminal.gwt.client.ui.form; -import com.google.gwt.dom.client.Style.Display; import com.google.gwt.event.dom.client.KeyDownEvent; import com.google.gwt.event.dom.client.KeyDownHandler; import com.google.gwt.event.shared.HandlerRegistration; @@ -27,7 +26,6 @@ public class VForm extends ComplexPanel implements KeyDownHandler { Widget lo; Element legend = DOM.createLegend(); Element caption = DOM.createSpan(); - private Element errorIndicatorElement = DOM.createDiv(); Element desc = DOM.createDiv(); Icon icon; VErrorMessage errorMessage = new VErrorMessage(); @@ -52,9 +50,6 @@ public class VForm extends ComplexPanel implements KeyDownHandler { setStyleName(CLASSNAME); fieldSet.appendChild(legend); legend.appendChild(caption); - errorIndicatorElement.setClassName("v-errorindicator"); - errorIndicatorElement.getStyle().setDisplay(Display.NONE); - errorIndicatorElement.setInnerText(" "); // needed for IE desc.setClassName("v-form-description"); fieldSet.appendChild(desc); // Adding description for initial padding // measurements, removed later if no diff --git a/src/com/vaadin/terminal/gwt/client/ui/formlayout/FormLayoutConnector.java b/src/com/vaadin/terminal/gwt/client/ui/formlayout/FormLayoutConnector.java index cdac73a771..ca21947a6c 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/formlayout/FormLayoutConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ui/formlayout/FormLayoutConnector.java @@ -46,9 +46,9 @@ public class FormLayoutConnector extends AbstractLayoutConnector { int childId = 0; - formLayoutTable.setRowCount(getChildren().size()); + formLayoutTable.setRowCount(getChildComponents().size()); - for (ComponentConnector child : getChildren()) { + for (ComponentConnector child : getChildComponents()) { Widget childWidget = child.getWidget(); Caption caption = formLayoutTable.getCaption(childWidget); diff --git a/src/com/vaadin/terminal/gwt/client/ui/formlayout/VFormLayout.java b/src/com/vaadin/terminal/gwt/client/ui/formlayout/VFormLayout.java index 85584755a6..8a859c409c 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/formlayout/VFormLayout.java +++ b/src/com/vaadin/terminal/gwt/client/ui/formlayout/VFormLayout.java @@ -35,7 +35,6 @@ public class VFormLayout extends SimplePanel { private final static String CLASSNAME = "v-formlayout"; - ApplicationConnection client; VFormLayoutTable table; public VFormLayout() { @@ -371,7 +370,7 @@ public class VFormLayout extends SimplePanel { public void onBrowserEvent(Event event) { super.onBrowserEvent(event); if (owner != null) { - client.handleTooltipEvent(event, owner); + owner.getConnection().handleTooltipEvent(event, owner); } } diff --git a/src/com/vaadin/terminal/gwt/client/ui/gridlayout/GridLayoutConnector.java b/src/com/vaadin/terminal/gwt/client/ui/gridlayout/GridLayoutConnector.java index 50afbc5913..2cd82313c2 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/gridlayout/GridLayoutConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ui/gridlayout/GridLayoutConnector.java @@ -63,7 +63,7 @@ public class GridLayoutConnector extends AbstractComponentContainerConnector layout.spacingMeasureElement); // Unregister caption size dependencies - for (ComponentConnector child : getChildren()) { + for (ComponentConnector child : getChildComponents()) { Cell cell = layout.widgetToCell.get(child.getWidget()); cell.slot.setCaption(null); } @@ -155,7 +155,7 @@ public class GridLayoutConnector extends AbstractComponentContainerConnector if (needCaptionUpdate) { needCaptionUpdate = false; - for (ComponentConnector child : getChildren()) { + for (ComponentConnector child : getChildComponents()) { updateCaption(child); } } diff --git a/src/com/vaadin/terminal/gwt/client/ui/helloworldfeature/GreetAgainRpc.java b/src/com/vaadin/terminal/gwt/client/ui/helloworldfeature/GreetAgainRpc.java new file mode 100644 index 0000000000..0bfb8f3c32 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/helloworldfeature/GreetAgainRpc.java @@ -0,0 +1,12 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.helloworldfeature; + +import com.vaadin.terminal.gwt.client.communication.ClientRpc; + +public interface GreetAgainRpc extends ClientRpc { + + public void greetAgain(); + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/helloworldfeature/HelloWorldExtensionConnector.java b/src/com/vaadin/terminal/gwt/client/ui/helloworldfeature/HelloWorldExtensionConnector.java new file mode 100644 index 0000000000..ff444fece5 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/helloworldfeature/HelloWorldExtensionConnector.java @@ -0,0 +1,48 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.helloworldfeature; + +import com.google.gwt.user.client.Window; +import com.vaadin.terminal.gwt.client.ServerConnector; +import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.VConsole; +import com.vaadin.terminal.gwt.client.communication.RpcProxy; +import com.vaadin.terminal.gwt.client.ui.AbstractConnector; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.ui.HelloWorldExtension; + +@Connect(HelloWorldExtension.class) +public class HelloWorldExtensionConnector extends AbstractConnector { + HelloWorldRpc rpc = RpcProxy.create(HelloWorldRpc.class, this); + + @Override + public HelloWorldState getState() { + return (HelloWorldState) super.getState(); + } + + @Override + protected void init() { + registerRpc(GreetAgainRpc.class, new GreetAgainRpc() { + public void greetAgain() { + greet(); + } + }); + } + + @Override + public void setParent(ServerConnector parent) { + super.setParent(parent); + greet(); + } + + private void greet() { + String msg = getState().getGreeting() + " from " + + Util.getConnectorString(this) + " attached to " + + Util.getConnectorString(getParent()); + VConsole.log(msg); + + String response = Window.prompt(msg, ""); + rpc.onMessageSent(response); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/helloworldfeature/HelloWorldRpc.java b/src/com/vaadin/terminal/gwt/client/ui/helloworldfeature/HelloWorldRpc.java new file mode 100644 index 0000000000..0289713390 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/helloworldfeature/HelloWorldRpc.java @@ -0,0 +1,10 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.helloworldfeature; + +import com.vaadin.terminal.gwt.client.communication.ServerRpc; + +public interface HelloWorldRpc extends ServerRpc { + public void onMessageSent(String message); +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/helloworldfeature/HelloWorldState.java b/src/com/vaadin/terminal/gwt/client/ui/helloworldfeature/HelloWorldState.java new file mode 100644 index 0000000000..9524a5e9aa --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/helloworldfeature/HelloWorldState.java @@ -0,0 +1,18 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.helloworldfeature; + +import com.vaadin.terminal.gwt.client.communication.SharedState; + +public class HelloWorldState extends SharedState { + private String greeting = "Hello world"; + + public String getGreeting() { + return greeting; + } + + public void setGreeting(String greeting) { + this.greeting = greeting; + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/label/ContentMode.java b/src/com/vaadin/terminal/gwt/client/ui/label/ContentMode.java index 2c232c9c38..4892c7e6bd 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/label/ContentMode.java +++ b/src/com/vaadin/terminal/gwt/client/ui/label/ContentMode.java @@ -6,7 +6,7 @@ package com.vaadin.terminal.gwt.client.ui.label; /** * Content modes defining how the client should interpret a Label's value. * - * @sine 7.0 + * @since 7.0.0 */ public enum ContentMode { /** diff --git a/src/com/vaadin/terminal/gwt/client/ui/layout/LayoutDependencyTree.java b/src/com/vaadin/terminal/gwt/client/ui/layout/LayoutDependencyTree.java index 1ca8933ff2..18843057f3 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/layout/LayoutDependencyTree.java +++ b/src/com/vaadin/terminal/gwt/client/ui/layout/LayoutDependencyTree.java @@ -13,6 +13,7 @@ import java.util.Set; import com.vaadin.terminal.gwt.client.ComponentConnector; import com.vaadin.terminal.gwt.client.ComponentContainerConnector; import com.vaadin.terminal.gwt.client.ComponentState; +import com.vaadin.terminal.gwt.client.ServerConnector; import com.vaadin.terminal.gwt.client.Util; import com.vaadin.terminal.gwt.client.VConsole; import com.vaadin.terminal.gwt.client.ui.ManagedLayout; @@ -154,9 +155,9 @@ public class LayoutDependencyTree { needsSize.add(connector); } if (!isRelativeInDirection(connector, direction)) { - ComponentConnector parent = connector.getParent(); - if (parent != null) { - needsSize.add(parent); + ServerConnector parent = connector.getParent(); + if (parent instanceof ComponentConnector) { + needsSize.add((ComponentConnector) parent); } } @@ -193,7 +194,7 @@ public class LayoutDependencyTree { if (connector instanceof ComponentContainerConnector) { ComponentContainerConnector container = (ComponentContainerConnector) connector; - for (ComponentConnector child : container.getChildren()) { + for (ComponentConnector child : container.getChildComponents()) { if (isRelativeInDirection(child, direction)) { resized.add(child); } @@ -246,16 +247,15 @@ public class LayoutDependencyTree { private LayoutDependency findPotentiallyChangedScrollbar() { ComponentConnector currentConnector = connector; while (true) { - ComponentContainerConnector parent = currentConnector - .getParent(); - if (parent == null) { + ServerConnector parent = currentConnector.getParent(); + if (!(parent instanceof ComponentConnector)) { return null; } if (parent instanceof MayScrollChildren) { return getDependency(currentConnector, getOppositeDirection()); } - currentConnector = parent; + currentConnector = (ComponentConnector) parent; } } @@ -504,12 +504,11 @@ public class LayoutDependencyTree { public ComponentConnector getScrollingBoundary(ComponentConnector connector) { LayoutDependency dependency = getDependency(connector, HORIZONTAL); if (!dependency.scrollingParentCached) { - ComponentContainerConnector parent = dependency.connector - .getParent(); + ServerConnector parent = dependency.connector.getParent(); if (parent instanceof MayScrollChildren) { dependency.scrollingBoundary = connector; - } else if (parent != null) { - dependency.scrollingBoundary = getScrollingBoundary(parent); + } else if (parent instanceof ComponentConnector) { + dependency.scrollingBoundary = getScrollingBoundary((ComponentConnector) parent); } else { // No scrolling parent } diff --git a/src/com/vaadin/terminal/gwt/client/ui/nativebutton/VNativeButton.java b/src/com/vaadin/terminal/gwt/client/ui/nativebutton/VNativeButton.java index 01ac4fab6a..dd6e741126 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/nativebutton/VNativeButton.java +++ b/src/com/vaadin/terminal/gwt/client/ui/nativebutton/VNativeButton.java @@ -127,11 +127,4 @@ public class VNativeButton extends Button implements ClickHandler { clickPending = false; } - @Override - public void setEnabled(boolean enabled) { - if (isEnabled() != enabled) { - super.setEnabled(enabled); - setStyleName(ApplicationConnection.DISABLED_CLASSNAME, !enabled); - } - } } diff --git a/src/com/vaadin/terminal/gwt/client/ui/notification/VNotification.java b/src/com/vaadin/terminal/gwt/client/ui/notification/VNotification.java index 0d222044ba..fb853b8a55 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/notification/VNotification.java +++ b/src/com/vaadin/terminal/gwt/client/ui/notification/VNotification.java @@ -43,6 +43,8 @@ public class VNotification extends VOverlay { public static final String STYLE_SYSTEM = "system"; private static final int FADE_ANIMATION_INTERVAL = 50; // == 20 fps + private static final ArrayList<VNotification> notifications = new ArrayList<VNotification>(); + private int startOpacity = 90; private int fadeMsec = 400; private int delayMsec = 1000; @@ -159,6 +161,7 @@ public class VNotification extends VOverlay { addStyleDependentName(style); } super.show(); + notifications.add(this); setPosition(position); /** * Android 4 fails to render notifications correctly without a little @@ -180,6 +183,7 @@ public class VNotification extends VOverlay { temporaryStyle = null; } super.hide(); + notifications.remove(this); fireEvent(new HideEvent(this)); } @@ -435,4 +439,19 @@ public class VNotification extends VOverlay { public interface EventListener extends java.util.EventListener { public void notificationHidden(HideEvent event); } + + /** + * Moves currently visible notifications to the top of the event preview + * stack. Can be called when opening other overlays such as subwindows to + * ensure the notifications receive the events they need and don't linger + * indefinitely. See #7136. + * + * TODO Should this be a generic Overlay feature instead? + */ + public static void bringNotificationsToFront() { + for (VNotification notification : notifications) { + DOM.removeEventPreview(notification); + DOM.addEventPreview(notification); + } + } } diff --git a/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/AbstractOrderedLayoutConnector.java b/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/AbstractOrderedLayoutConnector.java index d15766db21..9a89553fd2 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/AbstractOrderedLayoutConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/AbstractOrderedLayoutConnector.java @@ -61,7 +61,7 @@ public abstract class AbstractOrderedLayoutConnector extends lm.unregisterDependency(this, layout.spacingMeasureElement); // Unregister child caption listeners - for (ComponentConnector child : getChildren()) { + for (ComponentConnector child : getChildComponents()) { VLayoutSlot slot = layout.getSlotForChild(child.getWidget()); slot.setCaption(null); } @@ -105,7 +105,7 @@ public abstract class AbstractOrderedLayoutConnector extends VMeasuringOrderedLayout layout = getWidget(); - for (ComponentConnector child : getChildren()) { + for (ComponentConnector child : getChildComponents()) { VLayoutSlot slot = layout.getSlotForChild(child.getWidget()); AlignmentInfo alignment = new AlignmentInfo(getState() @@ -285,7 +285,7 @@ public abstract class AbstractOrderedLayoutConnector extends int currentIndex = 0; VMeasuringOrderedLayout layout = getWidget(); - for (ComponentConnector child : getChildren()) { + for (ComponentConnector child : getChildComponents()) { Widget childWidget = child.getWidget(); VLayoutSlot slot = layout.getSlotForChild(childWidget); diff --git a/src/com/vaadin/terminal/gwt/client/ui/panel/PanelConnector.java b/src/com/vaadin/terminal/gwt/client/ui/panel/PanelConnector.java index 35a0ede390..d9096526f3 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/panel/PanelConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ui/panel/PanelConnector.java @@ -111,6 +111,8 @@ public class PanelConnector extends AbstractComponentContainerConnector getWidget().captionNode.setClassName(captionClass); getWidget().contentNode.setClassName(contentClass); getWidget().bottomDecoration.setClassName(decoClass); + + getWidget().makeScrollable(); } if (!isRealUpdate(uidl)) { @@ -226,8 +228,8 @@ public class PanelConnector extends AbstractComponentContainerConnector super.onConnectorHierarchyChange(event); // We always have 1 child, unless the child is hidden Widget newChildWidget = null; - if (getChildren().size() == 1) { - ComponentConnector newChild = getChildren().get(0); + if (getChildComponents().size() == 1) { + ComponentConnector newChild = getChildComponents().get(0); newChildWidget = newChild.getWidget(); } diff --git a/src/com/vaadin/terminal/gwt/client/ui/panel/VPanel.java b/src/com/vaadin/terminal/gwt/client/ui/panel/VPanel.java index e2d3d443a0..6a06367acd 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/panel/VPanel.java +++ b/src/com/vaadin/terminal/gwt/client/ui/panel/VPanel.java @@ -6,8 +6,6 @@ package com.vaadin.terminal.gwt.client.ui.panel; import com.google.gwt.dom.client.DivElement; import com.google.gwt.dom.client.Document; -import com.google.gwt.event.dom.client.TouchStartEvent; -import com.google.gwt.event.dom.client.TouchStartHandler; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Element; import com.google.gwt.user.client.Event; @@ -18,6 +16,7 @@ import com.vaadin.terminal.gwt.client.ui.Icon; import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler; import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner; import com.vaadin.terminal.gwt.client.ui.TouchScrollDelegate; +import com.vaadin.terminal.gwt.client.ui.TouchScrollDelegate.TouchScrollHandler; public class VPanel extends SimplePanel implements ShortcutActionHandlerOwner, Focusable { @@ -46,7 +45,7 @@ public class VPanel extends SimplePanel implements ShortcutActionHandlerOwner, int scrollLeft; - private TouchScrollDelegate touchScrollDelegate; + private TouchScrollHandler touchScrollHandler; public VPanel() { super(); @@ -74,11 +73,11 @@ public class VPanel extends SimplePanel implements ShortcutActionHandlerOwner, setStyleName(CLASSNAME); DOM.sinkEvents(getElement(), Event.ONKEYDOWN); DOM.sinkEvents(contentNode, Event.ONSCROLL | Event.TOUCHEVENTS); - addHandler(new TouchStartHandler() { - public void onTouchStart(TouchStartEvent event) { - getTouchScrollDelegate().onTouchStart(event); - } - }, TouchStartEvent.getType()); + + contentNode.getStyle().setProperty("position", "relative"); + getElement().getStyle().setProperty("overflow", "hidden"); + + makeScrollable(); } /** @@ -100,6 +99,7 @@ public class VPanel extends SimplePanel implements ShortcutActionHandlerOwner, * * @see com.vaadin.terminal.gwt.client.Focusable#focus() */ + public void focus() { setFocus(true); @@ -174,16 +174,17 @@ public class VPanel extends SimplePanel implements ShortcutActionHandlerOwner, } } - protected TouchScrollDelegate getTouchScrollDelegate() { - if (touchScrollDelegate == null) { - touchScrollDelegate = new TouchScrollDelegate(contentNode); - } - return touchScrollDelegate; - - } - public ShortcutActionHandler getShortcutActionHandler() { return shortcutHandler; } + /** + * Ensures the panel is scrollable eg. after style name changes + */ + void makeScrollable() { + if (touchScrollHandler == null) { + touchScrollHandler = TouchScrollDelegate.enableTouchScrolling(this); + } + touchScrollHandler.addElement(contentNode); + } } diff --git a/src/com/vaadin/terminal/gwt/client/ui/root/PageClientRpc.java b/src/com/vaadin/terminal/gwt/client/ui/root/PageClientRpc.java new file mode 100644 index 0000000000..a02ecc8ded --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/root/PageClientRpc.java @@ -0,0 +1,13 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.root; + +import com.vaadin.terminal.gwt.client.communication.ClientRpc; + +public interface PageClientRpc extends ClientRpc { + + public void setTitle(String title); + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/root/RootConnector.java b/src/com/vaadin/terminal/gwt/client/ui/root/RootConnector.java index e02bcc9330..1a62e566ad 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/root/RootConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ui/root/RootConnector.java @@ -61,6 +61,11 @@ public class RootConnector extends AbstractComponentContainerConnector @Override protected void init() { super.init(); + registerRpc(PageClientRpc.class, new PageClientRpc() { + public void setTitle(String title) { + com.google.gwt.user.client.Window.setTitle(title); + } + }); } public void updateFromUIDL(final UIDL uidl, ApplicationConnection client) { @@ -93,12 +98,9 @@ public class RootConnector extends AbstractComponentContainerConnector } getWidget().setStyleName(styles.trim()); - clickEventHandler.handleEventHandlerRegistration(); + getWidget().makeScrollable(); - if (!getWidget().isEmbedded() && getState().getCaption() != null) { - // only change window title if we're in charge of the whole page - com.google.gwt.user.client.Window.setTitle(getState().getCaption()); - } + clickEventHandler.handleEventHandlerRegistration(); // Process children int childIndex = 0; @@ -168,9 +170,6 @@ public class RootConnector extends AbstractComponentContainerConnector getWidget().id, client); } getWidget().actionHandler.updateActionMap(childUidl); - } else if (tag == "execJS") { - String script = childUidl.getStringAttribute("script"); - VRoot.eval(script); } else if (tag == "notifications") { for (final Iterator<?> it = childUidl.getChildIterator(); it .hasNext();) { @@ -329,7 +328,7 @@ public class RootConnector extends AbstractComponentContainerConnector */ @Deprecated public boolean hasSubWindow(WindowConnector wc) { - return getChildren().contains(wc); + return getChildComponents().contains(wc); } /** @@ -340,7 +339,7 @@ public class RootConnector extends AbstractComponentContainerConnector */ public List<WindowConnector> getSubWindows() { ArrayList<WindowConnector> windows = new ArrayList<WindowConnector>(); - for (ComponentConnector child : getChildren()) { + for (ComponentConnector child : getChildComponents()) { if (child instanceof WindowConnector) { windows.add((WindowConnector) child); } @@ -380,7 +379,7 @@ public class RootConnector extends AbstractComponentContainerConnector onChildSizeChange(); } - for (ComponentConnector c : getChildren()) { + for (ComponentConnector c : getChildComponents()) { if (c instanceof WindowConnector) { WindowConnector wc = (WindowConnector) c; wc.setWindowOrderAndPosition(); diff --git a/src/com/vaadin/terminal/gwt/client/ui/root/VRoot.java b/src/com/vaadin/terminal/gwt/client/ui/root/VRoot.java index 12a69d5556..0af8919280 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/root/VRoot.java +++ b/src/com/vaadin/terminal/gwt/client/ui/root/VRoot.java @@ -7,6 +7,7 @@ package com.vaadin.terminal.gwt.client.ui.root; import java.util.ArrayList; import com.google.gwt.core.client.Scheduler.ScheduledCommand; +import com.google.gwt.dom.client.Element; import com.google.gwt.event.logical.shared.ResizeEvent; import com.google.gwt.event.logical.shared.ResizeHandler; import com.google.gwt.event.logical.shared.ValueChangeEvent; @@ -26,6 +27,8 @@ import com.vaadin.terminal.gwt.client.Focusable; import com.vaadin.terminal.gwt.client.VConsole; import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler; import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner; +import com.vaadin.terminal.gwt.client.ui.TouchScrollDelegate; +import com.vaadin.terminal.gwt.client.ui.TouchScrollDelegate.TouchScrollHandler; import com.vaadin.terminal.gwt.client.ui.VLazyExecutor; import com.vaadin.terminal.gwt.client.ui.textfield.VTextField; @@ -37,21 +40,36 @@ public class VRoot extends SimplePanel implements ResizeHandler, public static final String FRAGMENT_VARIABLE = "fragment"; + public static final String BROWSER_HEIGHT_VAR = "browserHeight"; + + public static final String BROWSER_WIDTH_VAR = "browserWidth"; + private static final String CLASSNAME = "v-view"; public static final String NOTIFICATION_HTML_CONTENT_NOT_ALLOWED = "useplain"; + private static int MONITOR_PARENT_TIMER_INTERVAL = 1000; + String theme; String id; ShortcutActionHandler actionHandler; - /** stored width for IE resize optimization */ - private int width; + /* + * Last known window size used to detect whether VView should be layouted + * again. Detection must check window size, because the VView size might be + * fixed and thus not automatically adapt to changed window sizes. + */ + private int windowWidth; + private int windowHeight; - /** stored height for IE resize optimization */ - private int height; + /* + * Last know view size used to detect whether new dimensions should be sent + * to the server. + */ + private int viewWidth; + private int viewHeight; ApplicationConnection connection; @@ -59,13 +77,19 @@ public class VRoot extends SimplePanel implements ResizeHandler, public static final String CLICK_EVENT_ID = "click"; /** - * 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). + * Keep track of possible parent size changes when an embedded application. + * + * Uses {@link #parentWidth} and {@link #parentHeight} as an optimization to + * keep track of when there is a real change. */ private Timer resizeTimer; + /** stored width of parent for embedded application auto-resize */ + private int parentWidth; + + /** stored height of parent for embedded application auto-resize */ + private int parentHeight; + int scrollTop; int scrollLeft; @@ -85,6 +109,8 @@ public class VRoot extends SimplePanel implements ResizeHandler, private HandlerRegistration historyHandlerRegistration; + private TouchScrollHandler touchScrollHandler; + /** * The current URI fragment, used to avoid sending updates if nothing has * changed. @@ -96,6 +122,7 @@ public class VRoot extends SimplePanel implements ResizeHandler, * whenever the value changes. */ private final ValueChangeHandler<String> historyChangeHandler = new ValueChangeHandler<String>() { + public void onValueChange(ValueChangeEvent<String> event) { String newFragment = event.getValue(); @@ -110,9 +137,9 @@ public class VRoot extends SimplePanel implements ResizeHandler, private VLazyExecutor delayedResizeExecutor = new VLazyExecutor(200, new ScheduledCommand() { + public void execute() { - windowSizeMaybeChanged(Window.getClientWidth(), - Window.getClientHeight()); + performSizeCheck(); } }); @@ -124,6 +151,29 @@ public class VRoot extends SimplePanel implements ResizeHandler, // Allow focusing the view by using the focus() method, the view // should not be in the document focus flow getElement().setTabIndex(-1); + makeScrollable(); + } + + /** + * Start to periodically monitor for parent element resizes if embedded + * application (e.g. portlet). + */ + @Override + protected void onLoad() { + super.onLoad(); + if (isMonitoringParentSize()) { + resizeTimer = new Timer() { + + @Override + public void run() { + // trigger check to see if parent size has changed, + // recalculate layouts + performSizeCheck(); + resizeTimer.schedule(MONITOR_PARENT_TIMER_INTERVAL); + } + }; + resizeTimer.schedule(MONITOR_PARENT_TIMER_INTERVAL); + } } @Override @@ -142,32 +192,95 @@ public class VRoot extends SimplePanel implements ResizeHandler, } /** - * Called when the window might have been resized. + * Stop monitoring for parent element resizes. + */ + + @Override + protected void onUnload() { + if (resizeTimer != null) { + resizeTimer.cancel(); + resizeTimer = null; + } + super.onUnload(); + } + + /** + * Called when the window or parent div might have been resized. * - * @param newWidth + * This immediately checks the sizes of the window and the parent div (if + * monitoring it) and triggers layout recalculation if they have changed. + */ + protected void performSizeCheck() { + windowSizeMaybeChanged(Window.getClientWidth(), + Window.getClientHeight()); + } + + /** + * Called when the window or parent div might have been resized. + * + * This immediately checks the sizes of the window and the parent div (if + * monitoring it) and triggers layout recalculation if they have changed. + * + * @param newWindowWidth * The new width of the window - * @param newHeight + * @param newWindowHeight * The new height of the window + * + * @deprecated use {@link #performSizeCheck()} */ - protected void windowSizeMaybeChanged(int newWidth, int newHeight) { + @Deprecated + protected void windowSizeMaybeChanged(int newWindowWidth, + int newWindowHeight) { boolean changed = false; ComponentConnector connector = ConnectorMap.get(connection) .getConnector(this); - if (width != newWidth) { - width = newWidth; + if (windowWidth != newWindowWidth) { + windowWidth = newWindowWidth; changed = true; - connector.getLayoutManager().reportOuterWidth(connector, newWidth); - VConsole.log("New window width: " + width); + connector.getLayoutManager().reportOuterWidth(connector, + newWindowWidth); + VConsole.log("New window width: " + windowWidth); } - if (height != newHeight) { - height = newHeight; + if (windowHeight != newWindowHeight) { + windowHeight = newWindowHeight; changed = true; - connector.getLayoutManager() - .reportOuterHeight(connector, newHeight); - VConsole.log("New window height: " + height); + connector.getLayoutManager().reportOuterHeight(connector, + newWindowHeight); + VConsole.log("New window height: " + windowHeight); + } + Element parentElement = getElement().getParentElement(); + if (isMonitoringParentSize() && parentElement != null) { + // check also for parent size changes + int newParentWidth = parentElement.getClientWidth(); + int newParentHeight = parentElement.getClientHeight(); + if (parentWidth != newParentWidth) { + parentWidth = newParentWidth; + changed = true; + VConsole.log("New parent width: " + parentWidth); + } + if (parentHeight != newParentHeight) { + parentHeight = newParentHeight; + changed = true; + VConsole.log("New parent height: " + parentHeight); + } } if (changed) { - VConsole.log("Running layout functions due to window resize"); + /* + * If the window size has changed, layout the VView again and send + * new size to the server if the size changed. (Just checking VView + * size would cause us to ignore cases when a relatively sized VView + * should shrink as the content's size is fixed and would thus not + * automatically shrink.) + */ + VConsole.log("Running layout functions due to window or parent resize"); + + // update size to avoid (most) redundant re-layout passes + // there can still be an extra layout recalculation if webkit + // overflow fix updates the size in a deferred block + if (isMonitoringParentSize() && parentElement != null) { + parentWidth = parentElement.getClientWidth(); + parentHeight = parentElement.getClientHeight(); + } sendClientResized(); @@ -188,21 +301,6 @@ public class VRoot extends SimplePanel implements ResizeHandler, }-*/; /** - * Evaluate the given script in the browser document. - * - * @param script - * Script to be executed. - */ - static native void eval(String script) - /*-{ - try { - if (script == null) return; - $wnd.eval(script); - } catch (e) { - } - }-*/; - - /** * Returns true if the body is NOT generated, i.e if someone else has made * the page that we're running in. Otherwise we're in charge of the whole * page. @@ -214,6 +312,17 @@ public class VRoot extends SimplePanel implements ResizeHandler, .contains(ApplicationConnection.GENERATED_BODY_CLASSNAME); } + /** + * Returns true if the size of the parent should be checked periodically and + * the application should react to its changes. + * + * @return true if size of parent should be tracked + */ + protected boolean isMonitoringParentSize() { + // could also perform a more specific check (Liferay portlet) + return isEmbedded(); + } + @Override public void onBrowserEvent(Event event) { super.onBrowserEvent(event); @@ -251,14 +360,19 @@ public class VRoot extends SimplePanel implements ResizeHandler, * com.google.gwt.event.logical.shared.ResizeHandler#onResize(com.google * .gwt.event.logical.shared.ResizeEvent) */ + public void onResize(ResizeEvent event) { - onResize(); + triggerSizeChangeCheck(); } /** * Called when a resize event is received. + * + * This may trigger a lazy refresh or perform the size check immediately + * depending on the browser used and whether the server side requests + * resizes to be lazy. */ - void onResize() { + private void triggerSizeChangeCheck() { /* * IE (pre IE9 at least) will give us some false resize events due to * problems with scrollbars. Firefox 3 might also produce some extra @@ -274,8 +388,7 @@ public class VRoot extends SimplePanel implements ResizeHandler, if (lazy) { delayedResizeExecutor.trigger(); } else { - windowSizeMaybeChanged(Window.getClientWidth(), - Window.getClientHeight()); + performSizeCheck(); } } @@ -283,8 +396,19 @@ public class VRoot extends SimplePanel implements ResizeHandler, * Send new dimensions to the server. */ private void sendClientResized() { - connection.updateVariable(id, "height", height, false); - connection.updateVariable(id, "width", width, immediate); + Element parentElement = getElement().getParentElement(); + int viewHeight = parentElement.getClientHeight(); + int viewWidth = parentElement.getClientWidth(); + + connection.updateVariable(id, "height", viewHeight, false); + connection.updateVariable(id, "width", viewWidth, false); + + int windowWidth = Window.getClientWidth(); + int windowHeight = Window.getClientHeight(); + + connection.updateVariable(id, BROWSER_WIDTH_VAR, windowWidth, false); + connection.updateVariable(id, BROWSER_HEIGHT_VAR, windowHeight, + immediate); } public native static void goTo(String url) @@ -319,4 +443,13 @@ public class VRoot extends SimplePanel implements ResizeHandler, getElement().focus(); } + /** + * Ensures the root is scrollable eg. after style name changes. + */ + void makeScrollable() { + if (touchScrollHandler == null) { + touchScrollHandler = TouchScrollDelegate.enableTouchScrolling(this); + } + touchScrollHandler.addElement(getElement()); + } } diff --git a/src/com/vaadin/terminal/gwt/client/ui/slider/VSlider.java b/src/com/vaadin/terminal/gwt/client/ui/slider/VSlider.java index 9ff614252d..5c7ee7a784 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/slider/VSlider.java +++ b/src/com/vaadin/terminal/gwt/client/ui/slider/VSlider.java @@ -18,6 +18,7 @@ import com.vaadin.terminal.gwt.client.BrowserInfo; import com.vaadin.terminal.gwt.client.ContainerResizedListener; import com.vaadin.terminal.gwt.client.Util; import com.vaadin.terminal.gwt.client.VConsole; +import com.vaadin.terminal.gwt.client.VTooltip; import com.vaadin.terminal.gwt.client.ui.Field; import com.vaadin.terminal.gwt.client.ui.SimpleFocusablePanel; import com.vaadin.terminal.gwt.client.ui.VLazyExecutor; @@ -51,6 +52,7 @@ public class VSlider extends SimpleFocusablePanel implements Field, private final HTML feedback = new HTML("", false); private final VOverlay feedbackPopup = new VOverlay(true, false, true) { + @Override public void show() { super.show(); @@ -112,6 +114,8 @@ public class VSlider extends SimpleFocusablePanel implements Field, feedbackPopup.addStyleName(CLASSNAME + "-feedback"); feedbackPopup.setWidget(feedback); + + sinkEvents(VTooltip.TOOLTIP_EVENTS); } void setFeedbackValue(double value) { @@ -139,8 +143,12 @@ public class VSlider extends SimpleFocusablePanel implements Field, void buildBase() { final String styleAttribute = vertical ? "height" : "width"; + final String oppositeStyleAttribute = vertical ? "width" : "height"; final String domProperty = vertical ? "offsetHeight" : "offsetWidth"; + // clear unnecessary opposite style attribute + DOM.setStyleAttribute(base, oppositeStyleAttribute, ""); + final Element p = DOM.getParent(getElement()); if (DOM.getElementPropertyInt(p, domProperty) > 50) { if (vertical) { @@ -153,6 +161,7 @@ public class VSlider extends SimpleFocusablePanel implements Field, // (supposedly) been drawn completely. DOM.setStyleAttribute(base, styleAttribute, MIN_SIZE + "px"); Scheduler.get().scheduleDeferred(new Command() { + public void execute() { final Element p = DOM.getParent(getElement()); if (DOM.getElementPropertyInt(p, domProperty) > (MIN_SIZE + 5)) { @@ -173,9 +182,14 @@ public class VSlider extends SimpleFocusablePanel implements Field, void buildHandle() { final String handleAttribute = vertical ? "marginTop" : "marginLeft"; + final String oppositeHandleAttribute = vertical ? "marginLeft" + : "marginTop"; DOM.setStyleAttribute(handle, handleAttribute, "0"); + // clear unnecessary opposite handle attribute + DOM.setStyleAttribute(handle, oppositeHandleAttribute, ""); + // Restore visibility DOM.setStyleAttribute(handle, "visibility", "visible"); @@ -277,6 +291,9 @@ public class VSlider extends SimpleFocusablePanel implements Field, event.preventDefault(); // avoid simulated events event.stopPropagation(); } + if (client != null) { + client.handleTooltipEvent(event, this); + } } private void processMouseWheelEvent(final Event event) { diff --git a/src/com/vaadin/terminal/gwt/client/ui/splitpanel/AbstractSplitPanelConnector.java b/src/com/vaadin/terminal/gwt/client/ui/splitpanel/AbstractSplitPanelConnector.java index a2f1f26c15..e33755bc9b 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/splitpanel/AbstractSplitPanelConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ui/splitpanel/AbstractSplitPanelConnector.java @@ -127,6 +127,12 @@ public abstract class AbstractSplitPanelConnector extends getWidget().setStylenames(); + getWidget().minimumPosition = splitterState.getMinPosition() + + splitterState.getMinPositionUnit(); + + getWidget().maximumPosition = splitterState.getMaxPosition() + + splitterState.getMaxPositionUnit(); + getWidget().position = splitterState.getPosition() + splitterState.getPositionUnit(); @@ -136,6 +142,7 @@ public abstract class AbstractSplitPanelConnector extends getLayoutManager().setNeedsLayout(this); + getWidget().makeScrollable(); } public void layout() { @@ -143,7 +150,7 @@ public abstract class AbstractSplitPanelConnector extends splitPanel.setSplitPosition(splitPanel.position); splitPanel.updateSizes(); // Report relative sizes in other direction for quicker propagation - List<ComponentConnector> children = getChildren(); + List<ComponentConnector> children = getChildComponents(); for (ComponentConnector child : children) { reportOtherDimension(child); } diff --git a/src/com/vaadin/terminal/gwt/client/ui/splitpanel/AbstractSplitPanelState.java b/src/com/vaadin/terminal/gwt/client/ui/splitpanel/AbstractSplitPanelState.java index 8b80eed840..db3a39d3a5 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/splitpanel/AbstractSplitPanelState.java +++ b/src/com/vaadin/terminal/gwt/client/ui/splitpanel/AbstractSplitPanelState.java @@ -49,6 +49,10 @@ public class AbstractSplitPanelState extends ComponentState { public static class SplitterState implements Serializable { private float position; private String positionUnit; + private float minPosition; + private String minPositionUnit; + private float maxPosition; + private String maxPositionUnit; private boolean positionReversed = false; private boolean locked = false; @@ -68,6 +72,38 @@ public class AbstractSplitPanelState extends ComponentState { this.positionUnit = positionUnit; } + public float getMinPosition() { + return minPosition; + } + + public void setMinPosition(float minPosition) { + this.minPosition = minPosition; + } + + public String getMinPositionUnit() { + return minPositionUnit; + } + + public void setMinPositionUnit(String minPositionUnit) { + this.minPositionUnit = minPositionUnit; + } + + public float getMaxPosition() { + return maxPosition; + } + + public void setMaxPosition(float maxPosition) { + this.maxPosition = maxPosition; + } + + public String getMaxPositionUnit() { + return maxPositionUnit; + } + + public void setMaxPositionUnit(String maxPositionUnit) { + this.maxPositionUnit = maxPositionUnit; + } + public boolean isPositionReversed() { return positionReversed; } diff --git a/src/com/vaadin/terminal/gwt/client/ui/splitpanel/VAbstractSplitPanel.java b/src/com/vaadin/terminal/gwt/client/ui/splitpanel/VAbstractSplitPanel.java index 166e79e92e..e2f30c6676 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/splitpanel/VAbstractSplitPanel.java +++ b/src/com/vaadin/terminal/gwt/client/ui/splitpanel/VAbstractSplitPanel.java @@ -4,6 +4,7 @@ package com.vaadin.terminal.gwt.client.ui.splitpanel; +import java.util.Collections; import java.util.List; import com.google.gwt.dom.client.Node; @@ -31,6 +32,7 @@ import com.vaadin.terminal.gwt.client.LayoutManager; import com.vaadin.terminal.gwt.client.Util; import com.vaadin.terminal.gwt.client.VConsole; import com.vaadin.terminal.gwt.client.ui.TouchScrollDelegate; +import com.vaadin.terminal.gwt.client.ui.TouchScrollDelegate.TouchScrollHandler; import com.vaadin.terminal.gwt.client.ui.VOverlay; import com.vaadin.terminal.gwt.client.ui.splitpanel.VAbstractSplitPanel.SplitterMoveHandler.SplitterMoveEvent; @@ -76,7 +78,7 @@ public class VAbstractSplitPanel extends ComplexPanel { private boolean positionReversed = false; - List<String> componentStyleNames; + List<String> componentStyleNames = Collections.emptyList(); private Element draggingCurtain; @@ -87,12 +89,16 @@ public class VAbstractSplitPanel extends ComplexPanel { /* The current position of the split handle in either percentages or pixels */ String position; + String maximumPosition; + + String minimumPosition; + + private TouchScrollHandler touchScrollHandler; + protected Element scrolledContainer; protected int origScrollTop; - private TouchScrollDelegate touchScrollDelegate; - public VAbstractSplitPanel() { this(ORIENTATION_HORIZONTAL); } @@ -116,6 +122,8 @@ public class VAbstractSplitPanel extends ComplexPanel { setOrientation(orientation); sinkEvents(Event.MOUSEEVENTS); + makeScrollable(); + addDomHandler(new TouchCancelHandler() { public void onTouchCancel(TouchCancelEvent event) { // TODO When does this actually happen?? @@ -127,11 +135,8 @@ public class VAbstractSplitPanel extends ComplexPanel { Node target = event.getTouches().get(0).getTarget().cast(); if (splitter.isOrHasChild(target)) { onMouseDown(Event.as(event.getNativeEvent())); - } else { - getTouchScrollDelegate().onTouchStart(event); } } - }, TouchStartEvent.getType()); addDomHandler(new TouchMoveHandler() { public void onTouchMove(TouchMoveEvent event) { @@ -150,14 +155,6 @@ public class VAbstractSplitPanel extends ComplexPanel { } - private TouchScrollDelegate getTouchScrollDelegate() { - if (touchScrollDelegate == null) { - touchScrollDelegate = new TouchScrollDelegate(firstContainer, - secondContainer); - } - return touchScrollDelegate; - } - protected void constructDom() { DOM.appendChild(splitter, DOM.createDiv()); // for styling DOM.appendChild(getElement(), wrapper); @@ -172,9 +169,7 @@ public class VAbstractSplitPanel extends ComplexPanel { DOM.setStyleAttribute(splitter, "position", "absolute"); DOM.setStyleAttribute(secondContainer, "position", "absolute"); - DOM.setStyleAttribute(firstContainer, "overflow", "auto"); - DOM.setStyleAttribute(secondContainer, "overflow", "auto"); - + setStylenames(); } private void setOrientation(int orientation) { @@ -190,11 +185,6 @@ public class VAbstractSplitPanel extends ComplexPanel { DOM.setStyleAttribute(firstContainer, "width", "100%"); DOM.setStyleAttribute(secondContainer, "width", "100%"); } - - DOM.setElementProperty(firstContainer, "className", CLASSNAME - + "-first-container"); - DOM.setElementProperty(secondContainer, "className", CLASSNAME - + "-second-container"); } @Override @@ -232,11 +222,109 @@ public class VAbstractSplitPanel extends ComplexPanel { } } + /** + * Converts given split position string (in pixels or percentage) to a + * floating point pixel value. + * + * @param pos + * @return + */ + private float convertToPixels(String pos) { + float posAsFloat; + if (pos.indexOf("%") > 0) { + posAsFloat = Math.round(Float.parseFloat(pos.substring(0, + pos.length() - 1)) + / 100 + * (orientation == ORIENTATION_HORIZONTAL ? getOffsetWidth() + : getOffsetHeight())); + } else { + posAsFloat = Float.parseFloat(pos.substring(0, pos.length() - 2)); + } + return posAsFloat; + } + + /** + * Converts given split position string (in pixels or percentage) to a float + * percentage value. + * + * @param pos + * @return + */ + private float convertToPercentage(String pos) { + float posAsFloat = 0; + + if (pos.indexOf("px") > 0) { + int posAsInt = Integer.parseInt(pos.substring(0, pos.length() - 2)); + int offsetLength = orientation == ORIENTATION_HORIZONTAL ? getOffsetWidth() + : getOffsetHeight(); + + // 100% needs special handling + if (posAsInt + getSplitterSize() >= offsetLength) { + posAsInt = offsetLength; + } + posAsFloat = ((float) posAsInt / (float) offsetLength * 100); + + } else { + posAsFloat = Float.parseFloat(pos.substring(0, pos.length() - 1)); + } + return posAsFloat; + } + + /** + * Returns the given position clamped to the range between current minimum + * and maximum positions. + * + * TODO Should this be in the connector? + * + * @param pos + * Position of the splitter as a CSS string, either pixels or a + * percentage. + * @return minimumPosition if pos is less than minimumPosition; + * maximumPosition if pos is greater than maximumPosition; pos + * otherwise. + */ + private String checkSplitPositionLimits(String pos) { + float positionAsFloat = convertToPixels(pos); + + if (maximumPosition != null + && convertToPixels(maximumPosition) < positionAsFloat) { + pos = maximumPosition; + } else if (minimumPosition != null + && convertToPixels(minimumPosition) > positionAsFloat) { + pos = minimumPosition; + } + return pos; + } + + /** + * Converts given string to the same units as the split position is. + * + * @param pos + * position to be converted + * @return converted position string + */ + private String convertToPositionUnits(String pos) { + if (position.indexOf("%") != -1 && pos.indexOf("%") == -1) { + // position is in percentage, pos in pixels + pos = convertToPercentage(pos) + "%"; + } else if (position.indexOf("px") > 0 && pos.indexOf("px") == -1) { + // position is in pixels and pos in percentage + pos = convertToPixels(pos) + "px"; + } + + return pos; + } + void setSplitPosition(String pos) { if (pos == null) { return; } + pos = checkSplitPositionLimits(pos); + if (!pos.equals(position)) { + position = convertToPositionUnits(pos); + } + // Convert percentage values to pixels if (pos.indexOf("%") > 0) { int size = orientation == ORIENTATION_HORIZONTAL ? getOffsetWidth() @@ -375,7 +463,6 @@ public class VAbstractSplitPanel extends ComplexPanel { } break; } - } void setFirstWidget(Widget w) { @@ -481,16 +568,7 @@ public class VAbstractSplitPanel extends ComplexPanel { } if (position.indexOf("%") > 0) { - float pos = newX; - // 100% needs special handling - if (newX + getSplitterSize() >= getOffsetWidth()) { - pos = getOffsetWidth(); - } - // Reversed position - if (positionReversed) { - pos = getOffsetWidth() - pos - getSplitterSize(); - } - position = (pos / getOffsetWidth() * 100) + "%"; + position = convertToPositionUnits(newX + "px"); } else { // Reversed position if (positionReversed) { @@ -523,16 +601,7 @@ public class VAbstractSplitPanel extends ComplexPanel { } if (position.indexOf("%") > 0) { - float pos = newY; - // 100% needs special handling - if (newY + getSplitterSize() >= getOffsetHeight()) { - pos = getOffsetHeight(); - } - // Reversed position - if (positionReversed) { - pos = getOffsetHeight() - pos - getSplitterSize(); - } - position = pos / getOffsetHeight() * 100 + "%"; + position = convertToPositionUnits(newY + "px"); } else { // Reversed position if (positionReversed) { @@ -659,30 +728,24 @@ public class VAbstractSplitPanel extends ComplexPanel { } void setStylenames() { - final String splitterSuffix = (orientation == ORIENTATION_HORIZONTAL ? "-hsplitter" - : "-vsplitter"); - final String firstContainerSuffix = "-first-container"; - final String secondContainerSuffix = "-second-container"; - String lockedSuffix = ""; - - String splitterStyle = CLASSNAME + splitterSuffix; - String firstStyle = CLASSNAME + firstContainerSuffix; - String secondStyle = CLASSNAME + secondContainerSuffix; - - if (locked) { - splitterStyle = CLASSNAME + splitterSuffix + "-locked"; - lockedSuffix = "-locked"; - } - for (String style : componentStyleNames) { - splitterStyle += " " + CLASSNAME + splitterSuffix + "-" + style - + lockedSuffix; - firstStyle += " " + CLASSNAME + firstContainerSuffix + "-" + style; - secondStyle += " " + CLASSNAME + secondContainerSuffix + "-" - + style; - } - DOM.setElementProperty(splitter, "className", splitterStyle); - DOM.setElementProperty(firstContainer, "className", firstStyle); - DOM.setElementProperty(secondContainer, "className", secondStyle); + final String splitterClass = CLASSNAME + + (orientation == ORIENTATION_HORIZONTAL ? "-hsplitter" + : "-vsplitter"); + final String firstContainerClass = CLASSNAME + "-first-container"; + final String secondContainerClass = CLASSNAME + "-second-container"; + final String lockedSuffix = locked ? "-locked" : ""; + + splitter.setClassName(splitterClass + lockedSuffix); + firstContainer.setClassName(firstContainerClass); + secondContainer.setClassName(secondContainerClass); + + for (String styleName : componentStyleNames) { + splitter.addClassName(splitterClass + "-" + styleName + + lockedSuffix); + firstContainer.addClassName(firstContainerClass + "-" + styleName); + secondContainer + .addClassName(secondContainerClass + "-" + styleName); + } } public void setEnabled(boolean enabled) { @@ -693,4 +756,14 @@ public class VAbstractSplitPanel extends ComplexPanel { return enabled; } + /** + * Ensures the panels are scrollable eg. after style name changes + */ + void makeScrollable() { + if (touchScrollHandler == null) { + touchScrollHandler = TouchScrollDelegate.enableTouchScrolling(this); + } + touchScrollHandler.addElement(firstContainer); + touchScrollHandler.addElement(secondContainer); + } } diff --git a/src/com/vaadin/terminal/gwt/client/ui/table/TableConnector.java b/src/com/vaadin/terminal/gwt/client/ui/table/TableConnector.java index 73bc5da83f..ada0f2424f 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/table/TableConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ui/table/TableConnector.java @@ -16,6 +16,7 @@ import com.vaadin.terminal.gwt.client.BrowserInfo; import com.vaadin.terminal.gwt.client.ComponentConnector; import com.vaadin.terminal.gwt.client.DirectionalManagedLayout; import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.ServerConnector; import com.vaadin.terminal.gwt.client.UIDL; import com.vaadin.terminal.gwt.client.Util; import com.vaadin.terminal.gwt.client.ui.AbstractComponentContainerConnector; @@ -283,8 +284,11 @@ public class TableConnector extends AbstractComponentContainerConnector Scheduler.get().scheduleFinally(new ScheduledCommand() { public void execute() { getLayoutManager().setNeedsMeasure(TableConnector.this); - getLayoutManager().setNeedsMeasure( - TableConnector.this.getParent()); + ServerConnector parent = getParent(); + if (parent instanceof ComponentConnector) { + getLayoutManager().setNeedsMeasure( + (ComponentConnector) parent); + } getLayoutManager().setNeedsVerticalLayout( TableConnector.this); getLayoutManager().layoutNow(); diff --git a/src/com/vaadin/terminal/gwt/client/ui/table/VScrollTable.java b/src/com/vaadin/terminal/gwt/client/ui/table/VScrollTable.java index c45c26c4ac..c4a57f5c8b 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/table/VScrollTable.java +++ b/src/com/vaadin/terminal/gwt/client/ui/table/VScrollTable.java @@ -44,8 +44,6 @@ import com.google.gwt.event.dom.client.KeyUpEvent; import com.google.gwt.event.dom.client.KeyUpHandler; import com.google.gwt.event.dom.client.ScrollEvent; import com.google.gwt.event.dom.client.ScrollHandler; -import com.google.gwt.event.dom.client.TouchStartEvent; -import com.google.gwt.event.dom.client.TouchStartHandler; import com.google.gwt.event.logical.shared.CloseEvent; import com.google.gwt.event.logical.shared.CloseHandler; import com.google.gwt.user.client.Command; @@ -236,6 +234,12 @@ public class VScrollTable extends FlowPanel implements HasWidgets, private boolean enableDebug = false; + private static final boolean hasNativeTouchScrolling = BrowserInfo.get() + .isTouchDevice() + && !BrowserInfo.get().requiresTouchScrollDelegate(); + + private Set<String> noncollapsibleColumns; + /** * Represents a select range of rows */ @@ -268,6 +272,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * * @see java.lang.Object#toString() */ + @Override public String toString() { return startRow.getKey() + "-" + length; @@ -321,6 +326,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, final FocusableScrollPanel scrollBodyPanel = new FocusableScrollPanel(true); private KeyPressHandler navKeyPressHandler = new KeyPressHandler() { + public void onKeyPress(KeyPressEvent keyPressEvent) { // This is used for Firefox only, since Firefox auto-repeat // works correctly only if we use a key press handler, other @@ -489,12 +495,12 @@ public class VScrollTable extends FlowPanel implements HasWidgets, public VScrollTable() { setMultiSelectMode(MULTISELECT_MODE_DEFAULT); - scrollBodyPanel.setStyleName(CLASSNAME + "-body-wrapper"); + scrollBodyPanel.addStyleName(CLASSNAME + "-body-wrapper"); scrollBodyPanel.addFocusHandler(this); scrollBodyPanel.addBlurHandler(this); scrollBodyPanel.addScrollHandler(this); - scrollBodyPanel.setStyleName(CLASSNAME + "-body"); + scrollBodyPanel.addStyleName(CLASSNAME + "-body"); /* * Firefox auto-repeat works correctly only if we use a key press @@ -509,14 +515,10 @@ public class VScrollTable extends FlowPanel implements HasWidgets, scrollBodyPanel.addKeyUpHandler(navKeyUpHandler); scrollBodyPanel.sinkEvents(Event.TOUCHEVENTS); - scrollBodyPanel.addDomHandler(new TouchStartHandler() { - public void onTouchStart(TouchStartEvent event) { - getTouchScrollDelegate().onTouchStart(event); - } - }, TouchStartEvent.getType()); scrollBodyPanel.sinkEvents(Event.ONCONTEXTMENU); scrollBodyPanel.addDomHandler(new ContextMenuHandler() { + public void onContextMenu(ContextMenuEvent event) { handleBodyContextMenu(event); } @@ -536,22 +538,13 @@ public class VScrollTable extends FlowPanel implements HasWidgets, // Add a handler to clear saved context menu details when the menu // closes. See #8526. client.getContextMenu().addCloseHandler(new CloseHandler<PopupPanel>() { + public void onClose(CloseEvent<PopupPanel> event) { contextMenu = null; } }); } - protected TouchScrollDelegate getTouchScrollDelegate() { - if (touchScrollDelegate == null) { - touchScrollDelegate = new TouchScrollDelegate( - scrollBodyPanel.getElement()); - touchScrollDelegate.setScrollHandler(this); - } - return touchScrollDelegate; - - } - private void handleBodyContextMenu(ContextMenuEvent event) { if (enabled && bodyActionKeys != null) { int left = Util.getTouchOrMouseClientX(event.getNativeEvent()); @@ -896,6 +889,10 @@ public class VScrollTable extends FlowPanel implements HasWidgets, updateHeader(uidl.getStringArrayAttribute("vcolorder")); updateFooter(uidl.getStringArrayAttribute("vcolorder")); + if (uidl.hasVariable("noncollapsiblecolumns")) { + noncollapsibleColumns = uidl + .getStringArrayVariableAsSet("noncollapsiblecolumns"); + } } private void updateCollapsedColumns(UIDL uidl) { @@ -1848,12 +1845,9 @@ public class VScrollTable extends FlowPanel implements HasWidgets, isNewBody = false; if (firstvisible > 0) { - // FIXME #7607 - // Originally deferred due to Firefox oddities which should not - // occur any more. Currently deferring breaks Webkit scrolling with - // relative-height tables, but not deferring instead breaks tables - // with explicit page length. + // Deferred due to some Firefox oddities Scheduler.get().scheduleDeferred(new Command() { + public void execute() { scrollBodyPanel .setScrollPosition(measureRowHeightOffset(firstvisible)); @@ -1888,6 +1882,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, */ scrollBody.reLayoutComponents(); Scheduler.get().scheduleDeferred(new Command() { + public void execute() { Util.runWebkitOverflowAutoFix(scrollBodyPanel.getElement()); } @@ -2218,6 +2213,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, setWidth(tdWidth + "px"); } else { Scheduler.get().scheduleDeferred(new Command() { + public void execute() { int tdWidth = width + scrollBody.getCellExtraWidth(); @@ -2271,6 +2267,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Handle column reordering. */ + @Override public void onBrowserEvent(Event event) { if (enabled && event != null) { @@ -2814,6 +2811,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, final int newWidth = width; Scheduler.get().scheduleDeferred( new ScheduledCommand() { + public void execute() { setColWidth(colIx, newWidth, true); } @@ -2841,6 +2839,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, if (refreshContentWidths) { // Recalculate the column sizings if any column has changed Scheduler.get().scheduleDeferred(new ScheduledCommand() { + public void execute() { triggerLazyColumnAdjustment(true); } @@ -3029,6 +3028,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, String colKey; private boolean collapsed; + private boolean noncollapsible = false; private VScrollTableRow currentlyFocusedRow; public VisibleColumnAction(String colKey) { @@ -3040,6 +3040,9 @@ public class VScrollTable extends FlowPanel implements HasWidgets, @Override public void execute() { + if (noncollapsible) { + return; + } client.getContextMenu().hide(); // toggle selected column if (collapsedColumns.contains(colKey)) { @@ -3063,17 +3066,28 @@ public class VScrollTable extends FlowPanel implements HasWidgets, collapsed = b; } + public void setNoncollapsible(boolean b) { + noncollapsible = b; + } + /** * Override default method to distinguish on/off columns */ + @Override public String getHTML() { final StringBuffer buf = new StringBuffer(); + buf.append("<span class=\""); if (collapsed) { - buf.append("<span class=\"v-off\">"); + buf.append("v-off"); } else { - buf.append("<span class=\"v-on\">"); + buf.append("v-on"); + } + if (noncollapsible) { + buf.append(" v-disabled"); } + buf.append("\">"); + buf.append(super.getHTML()); buf.append("</span>"); @@ -3085,6 +3099,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /* * Returns columns as Action array for column select popup */ + public Action[] getActions() { Object[] cols; if (columnReordering && columnOrder != null) { @@ -3115,6 +3130,9 @@ public class VScrollTable extends FlowPanel implements HasWidgets, if (!c.isEnabled()) { a.setCollapsed(true); } + if (noncollapsibleColumns.contains(cid)) { + a.setNoncollapsible(true); + } actions[i] = a; } return actions; @@ -3200,6 +3218,10 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * The text in the footer */ public void setText(String footerText) { + if (footerText == null || footerText.equals("")) { + footerText = " "; + } + DOM.setInnerHTML(captionContainer, footerText); } @@ -3295,6 +3317,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, setWidth(tdWidth + "px"); } else { Scheduler.get().scheduleDeferred(new Command() { + public void execute() { int borderWidths = 1; int tdWidth = width @@ -3541,6 +3564,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * com.google.gwt.user.client.ui.Panel#remove(com.google.gwt.user.client * .ui.Widget) */ + @Override public boolean remove(Widget w) { if (visibleCells.contains(w)) { @@ -3557,6 +3581,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * * @see com.google.gwt.user.client.ui.HasWidgets#iterator() */ + public Iterator<Widget> iterator() { return visibleCells.iterator(); } @@ -3835,7 +3860,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, DOM.appendChild(container, preSpacer); DOM.appendChild(container, table); DOM.appendChild(container, postSpacer); - if (BrowserInfo.get().isTouchDevice()) { + if (BrowserInfo.get().requiresTouchScrollDelegate()) { NodeList<Node> childNodes = container.getChildNodes(); for (int i = 0; i < childNodes.getLength(); i++) { Element item = (Element) childNodes.getItem(i); @@ -4388,7 +4413,6 @@ public class VScrollTable extends FlowPanel implements HasWidgets, private String[] actionKeys = null; private final TableRowElement rowElement; - private boolean mDown; private int index; private Event touchStart; private static final String ROW_CLASSNAME_EVEN = CLASSNAME + "-row"; @@ -4396,8 +4420,10 @@ public class VScrollTable extends FlowPanel implements HasWidgets, + "-row-odd"; private static final int TOUCH_CONTEXT_MENU_TIMEOUT = 500; private Timer contextTouchTimeout; + private Timer dragTouchTimeout; private int touchStartY; private int touchStartX; + private boolean isDragging = false; private VScrollTableRow(int rowKey) { this.rowKey = rowKey; @@ -4785,12 +4811,128 @@ public class VScrollTable extends FlowPanel implements HasWidgets, } } + /** + * Special handler for touch devices that support native scrolling + * + * @return Whether the event was handled by this method. + */ + private boolean handleTouchEvent(final Event event) { + + boolean touchEventHandled = false; + + if (enabled && hasNativeTouchScrolling) { + final Element targetTdOrTr = getEventTargetTdOrTr(event); + final int type = event.getTypeInt(); + + switch (type) { + case Event.ONTOUCHSTART: + touchEventHandled = true; + touchStart = event; + isDragging = false; + Touch touch = event.getChangedTouches().get(0); + // save position to fields, touches in events are same + // instance during the operation. + touchStartX = touch.getClientX(); + touchStartY = touch.getClientY(); + + if (dragmode != 0) { + if (dragTouchTimeout == null) { + dragTouchTimeout = new Timer() { + + @Override + public void run() { + if (touchStart != null) { + // Start a drag if a finger is held + // in place long enough, then moved + isDragging = true; + } + } + }; + } + dragTouchTimeout.schedule(TOUCHSCROLL_TIMEOUT); + } + + if (actionKeys != null) { + if (contextTouchTimeout == null) { + contextTouchTimeout = new Timer() { + + @Override + public void run() { + if (touchStart != null) { + // Open the context menu if finger + // is held in place long enough. + showContextMenu(touchStart); + event.preventDefault(); + touchStart = null; + } + } + }; + } + contextTouchTimeout + .schedule(TOUCH_CONTEXT_MENU_TIMEOUT); + } + break; + case Event.ONTOUCHMOVE: + touchEventHandled = true; + if (isSignificantMove(event)) { + if (contextTouchTimeout != null) { + // Moved finger before the context menu timer + // expired, so let the browser handle this as a + // scroll. + contextTouchTimeout.cancel(); + contextTouchTimeout = null; + } + if (!isDragging && dragTouchTimeout != null) { + // Moved finger before the drag timer expired, + // so let the browser handle this as a scroll. + dragTouchTimeout.cancel(); + dragTouchTimeout = null; + } + + if (dragmode != 0 && touchStart != null + && isDragging) { + event.preventDefault(); + event.stopPropagation(); + startRowDrag(touchStart, type, targetTdOrTr); + } + touchStart = null; + } + break; + case Event.ONTOUCHEND: + case Event.ONTOUCHCANCEL: + touchEventHandled = true; + if (contextTouchTimeout != null) { + contextTouchTimeout.cancel(); + } + if (dragTouchTimeout != null) { + dragTouchTimeout.cancel(); + } + if (touchStart != null) { + event.preventDefault(); + event.stopPropagation(); + if (!BrowserInfo.get().isAndroid()) { + Util.simulateClickFromTouchEvent(touchStart, + this); + } + touchStart = null; + } + isDragging = false; + break; + } + } + return touchEventHandled; + } + /* * React on click that occur on content cells only */ + @Override public void onBrowserEvent(final Event event) { - if (enabled) { + + final boolean touchEventHandled = handleTouchEvent(event); + + if (enabled && !touchEventHandled) { final int type = event.getTypeInt(); final Element targetTdOrTr = getEventTargetTdOrTr(event); if (type == Event.ONCONTEXTMENU) { @@ -4823,7 +4965,6 @@ public class VScrollTable extends FlowPanel implements HasWidgets, break; case Event.ONMOUSEUP: if (targetCellOrRowFound) { - mDown = false; /* * Queue here, send at the same time as the * corresponding value change event - see #7127 @@ -4983,6 +5124,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, touchStart.preventDefault(); if (dragmode != 0 || actionKeys != null) { new Timer() { + @Override public void run() { TouchScrollDelegate activeScrollDelegate = TouchScrollDelegate @@ -5021,6 +5163,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, if (contextTouchTimeout == null && actionKeys != null) { contextTouchTimeout = new Timer() { + @Override public void run() { if (touchStart != null) { @@ -5066,9 +5209,6 @@ public class VScrollTable extends FlowPanel implements HasWidgets, } break; case Event.ONMOUSEOUT: - if (targetCellOrRowFound) { - mDown = false; - } break; default: break; @@ -5098,7 +5238,6 @@ public class VScrollTable extends FlowPanel implements HasWidgets, protected void startRowDrag(Event event, final int type, Element targetTdOrTr) { - mDown = true; VTransferable transferable = new VTransferable(); transferable.setDragSource(ConnectorMap.get(client) .getConnector(VScrollTable.this)); @@ -5290,6 +5429,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * * @see com.vaadin.terminal.gwt.client.ui.IActionOwner#getActions () */ + public Action[] getActions() { if (actionKeys == null) { return new Action[] {}; @@ -5299,6 +5439,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, final String actionKey = actionKeys[i]; final TreeAction a = new TreeAction(this, String.valueOf(rowKey), actionKey) { + @Override public void execute() { super.execute(); @@ -5586,6 +5727,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * column widths "optimally". Doing this lazily to avoid expensive * calculation when resizing is not yet finished. */ + @Override public void run() { if (scrollBody == null) { @@ -5682,7 +5824,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, if (isDynamicHeight() && totalRows == pageLength) { // fix body height (may vary if lazy loading is offhorizontal // scrollbar appears/disappears) - int bodyHeight = scrollBody.getRequiredHeight(); + int bodyHeight = Util.getRequiredHeight(scrollBody); boolean needsSpaceForHorizontalScrollbar = (availW < usedMinimumWidth); if (needsSpaceForHorizontalScrollbar) { bodyHeight += Util.getNativeScrollbarSize(); @@ -5695,6 +5837,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, } scrollBody.reLayoutComponents(); Scheduler.get().scheduleDeferred(new Command() { + public void execute() { Util.runWebkitOverflowAutoFix(scrollBodyPanel.getElement()); } @@ -5804,15 +5947,26 @@ public class VScrollTable extends FlowPanel implements HasWidgets, void updateHeight() { setContainerHeight(); - updatePageLength(); - + if (initializedAndAttached) { + updatePageLength(); + } if (!rendering) { // Webkit may sometimes get an odd rendering bug (white space // between header and body), see bug #3875. Running // overflow hack here to shake body element a bit. - Util.runWebkitOverflowAutoFix(scrollBodyPanel.getElement()); + // We must run the fix as a deferred command to prevent it from + // overwriting the scroll position with an outdated value, see + // #7607. + Scheduler.get().scheduleDeferred(new Command() { + + public void execute() { + Util.runWebkitOverflowAutoFix(scrollBodyPanel.getElement()); + } + }); } + triggerLazyColumnAdjustment(false); + /* * setting height may affect wheter the component has scrollbars -> * needs scrolling or not @@ -5826,6 +5980,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * lost). Example ITabPanel just set contained components invisible and back * when changing tabs. */ + @Override public void setVisible(boolean visible) { if (isVisible() != visible) { @@ -5833,6 +5988,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, if (initializedAndAttached) { if (visible) { Scheduler.get().scheduleDeferred(new Command() { + public void execute() { scrollBodyPanel .setScrollPosition(measureRowHeightOffset(firstRowInViewPort)); @@ -5866,6 +6022,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * This method has logic which rows needs to be requested from server when * user scrolls */ + public void onScroll(ScrollEvent event) { scrollLeft = scrollBodyPanel.getElement().getScrollLeft(); scrollTop = scrollBodyPanel.getScrollPosition(); @@ -5894,6 +6051,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, // correct // value available soon. Scheduler.get().scheduleDeferred(new Command() { + public void execute() { onScroll(null); } @@ -5994,7 +6152,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, return false; } - // @Override + // // public int hashCode() { // return overkey; // } @@ -6053,6 +6211,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, deEmphasis(); final TableDDDetails newDetails = dropDetails; VAcceptCallback cb = new VAcceptCallback() { + public void accepted(VDragEvent event) { if (newDetails.equals(dropDetails)) { dragAccepted(event); @@ -6459,6 +6618,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event * .dom.client.FocusEvent) */ + public void onFocus(FocusEvent event) { if (isFocusable()) { hasFocus = true; @@ -6479,6 +6639,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * com.google.gwt.event.dom.client.BlurHandler#onBlur(com.google.gwt.event * .dom.client.BlurEvent) */ + public void onBlur(BlurEvent event) { hasFocus = false; navKeyDown = false; @@ -6551,6 +6712,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * * @see com.vaadin.terminal.gwt.client.Focusable#focus() */ + public void focus() { if (isFocusable()) { scrollBodyPanel.focus(); @@ -6594,6 +6756,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, public void startScrollingVelocityTimer() { if (scrollingVelocityTimer == null) { scrollingVelocityTimer = new Timer() { + @Override public void run() { scrollingVelocity++; @@ -6630,6 +6793,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, public void lazyRevertFocusToRow(final VScrollTableRow currentlyFocusedRow) { Scheduler.get().scheduleFinally(new ScheduledCommand() { + public void execute() { if (currentlyFocusedRow != null) { setRowFocus(currentlyFocusedRow); diff --git a/src/com/vaadin/terminal/gwt/client/ui/tabsheet/VTabsheet.java b/src/com/vaadin/terminal/gwt/client/ui/tabsheet/VTabsheet.java index c97ede1252..aba5a41f9a 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/tabsheet/VTabsheet.java +++ b/src/com/vaadin/terminal/gwt/client/ui/tabsheet/VTabsheet.java @@ -331,6 +331,11 @@ public class VTabsheet extends VTabsheetBase implements Focusable, } return width; } + + public Element getCloseButton() { + return closeButton; + } + } static class TabBar extends ComplexPanel implements ClickHandler, @@ -393,7 +398,14 @@ public class VTabsheet extends VTabsheetBase implements Focusable, } public void onClick(ClickEvent event) { - Widget caption = (Widget) event.getSource(); + TabCaption caption = (TabCaption) event.getSource(); + Element targetElement = event.getNativeEvent().getEventTarget() + .cast(); + // the tab should not be focused if the close button was clicked + if (targetElement == caption.getCloseButton()) { + return; + } + int index = getWidgetIndex(caption.getParent()); // IE needs explicit focus() if (BrowserInfo.get().isIE()) { @@ -1021,6 +1033,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable, final Style style = scroller.getStyle(); style.setProperty("whiteSpace", "normal"); Scheduler.get().scheduleDeferred(new Command() { + public void execute() { style.setProperty("whiteSpace", ""); } diff --git a/src/com/vaadin/terminal/gwt/client/ui/tabsheet/VTabsheetPanel.java b/src/com/vaadin/terminal/gwt/client/ui/tabsheet/VTabsheetPanel.java index f2b37c3a1c..bd6cddb682 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/tabsheet/VTabsheetPanel.java +++ b/src/com/vaadin/terminal/gwt/client/ui/tabsheet/VTabsheetPanel.java @@ -4,16 +4,12 @@ package com.vaadin.terminal.gwt.client.ui.tabsheet; -import com.google.gwt.dom.client.Node; -import com.google.gwt.dom.client.NodeList; -import com.google.gwt.event.dom.client.TouchStartEvent; -import com.google.gwt.event.dom.client.TouchStartHandler; 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.ComplexPanel; import com.google.gwt.user.client.ui.Widget; import com.vaadin.terminal.gwt.client.ui.TouchScrollDelegate; +import com.vaadin.terminal.gwt.client.ui.TouchScrollDelegate.TouchScrollHandler; /** * A panel that displays all of its child widgets in a 'deck', where only one @@ -27,38 +23,15 @@ import com.vaadin.terminal.gwt.client.ui.TouchScrollDelegate; public class VTabsheetPanel extends ComplexPanel { private Widget visibleWidget; - private TouchScrollDelegate touchScrollDelegate; + + private final TouchScrollHandler touchScrollHandler; /** * Creates an empty tabsheet panel. */ public VTabsheetPanel() { setElement(DOM.createDiv()); - sinkEvents(Event.TOUCHEVENTS); - addDomHandler(new TouchStartHandler() { - public void onTouchStart(TouchStartEvent event) { - /* - * All container elements needs to be scrollable by one finger. - * Update the scrollable element list of touch delegate on each - * touch start. - */ - NodeList<Node> childNodes = getElement().getChildNodes(); - Element[] elements = new Element[childNodes.getLength()]; - for (int i = 0; i < elements.length; i++) { - elements[i] = (Element) childNodes.getItem(i); - } - getTouchScrollDelegate().setElements(elements); - getTouchScrollDelegate().onTouchStart(event); - } - }, TouchStartEvent.getType()); - } - - protected TouchScrollDelegate getTouchScrollDelegate() { - if (touchScrollDelegate == null) { - touchScrollDelegate = new TouchScrollDelegate(); - } - return touchScrollDelegate; - + touchScrollHandler = TouchScrollDelegate.enableTouchScrolling(this); } /** @@ -77,8 +50,8 @@ public class VTabsheetPanel extends ComplexPanel { private Element createContainerElement() { Element el = DOM.createDiv(); DOM.setStyleAttribute(el, "position", "absolute"); - DOM.setStyleAttribute(el, "overflow", "auto"); hide(el); + touchScrollHandler.addElement(el); return el; } @@ -122,6 +95,7 @@ public class VTabsheetPanel extends ComplexPanel { if (parent != null) { DOM.removeChild(getElement(), parent); } + touchScrollHandler.removeElement(parent); } return removed; } @@ -141,6 +115,8 @@ public class VTabsheetPanel extends ComplexPanel { hide(DOM.getParent(visibleWidget.getElement())); } visibleWidget = newVisible; + touchScrollHandler.setElements(visibleWidget.getElement() + .getParentElement()); } // Always ensure the selected tab is visible. If server prevents a tab // change we might end up here with visibleWidget == newVisible but its diff --git a/src/com/vaadin/terminal/gwt/client/ui/treetable/VTreeTable.java b/src/com/vaadin/terminal/gwt/client/ui/treetable/VTreeTable.java index f7cd9d133e..9a8e0e9ce1 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/treetable/VTreeTable.java +++ b/src/com/vaadin/terminal/gwt/client/ui/treetable/VTreeTable.java @@ -379,26 +379,34 @@ public class VTreeTable extends VScrollTable { rowsToDelete.add(row); } } - RowCollapseAnimation anim = new RowCollapseAnimation(rowsToDelete) { - @Override - protected void onComplete() { - super.onComplete(); - // Actually unlink the rows and update the cache after the - // animation is done. - unlinkAndReindexRows(firstIndex, rows); - discardRowsOutsideCacheWindow(); - ensureCacheFilled(); - } - }; - anim.run(150); + if (!rowsToDelete.isEmpty()) { + // #8810 Only animate if there's something to animate + RowCollapseAnimation anim = new RowCollapseAnimation( + rowsToDelete) { + @Override + protected void onComplete() { + super.onComplete(); + // Actually unlink the rows and update the cache after + // the + // animation is done. + unlinkAndReindexRows(firstIndex, rows); + discardRowsOutsideCacheWindow(); + ensureCacheFilled(); + } + }; + anim.run(150); + } } protected List<VScrollTableRow> insertRowsAnimated(UIDL rowData, int firstIndex, int rows) { List<VScrollTableRow> insertedRows = insertAndReindexRows(rowData, firstIndex, rows); - RowExpandAnimation anim = new RowExpandAnimation(insertedRows); - anim.run(150); + if (!insertedRows.isEmpty()) { + // Only animate if there's something to animate (#8810) + RowExpandAnimation anim = new RowExpandAnimation(insertedRows); + anim.run(150); + } return insertedRows; } @@ -521,6 +529,10 @@ public class VTreeTable extends VScrollTable { private Element cloneTable; private AnimationPreparator preparator; + /** + * @param rows + * List of rows to animate. Must not be empty. + */ public RowExpandAnimation(List<VScrollTableRow> rows) { this.rows = rows; buildAndInsertAnimatingDiv(); @@ -641,6 +653,10 @@ public class VTreeTable extends VScrollTable { private final List<VScrollTableRow> rows; + /** + * @param rows + * List of rows to animate. Must not be empty. + */ public RowCollapseAnimation(List<VScrollTableRow> rows) { super(rows); this.rows = rows; diff --git a/src/com/vaadin/terminal/gwt/client/ui/window/VWindow.java b/src/com/vaadin/terminal/gwt/client/ui/window/VWindow.java index d08387fc6d..8fd84a9ea6 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/window/VWindow.java +++ b/src/com/vaadin/terminal/gwt/client/ui/window/VWindow.java @@ -40,6 +40,7 @@ import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler; import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner; import com.vaadin.terminal.gwt.client.ui.VLazyExecutor; import com.vaadin.terminal.gwt.client.ui.VOverlay; +import com.vaadin.terminal.gwt.client.ui.notification.VNotification; /** * "Sub window" component. @@ -264,8 +265,10 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, if (!orderingDefered) { orderingDefered = true; Scheduler.get().scheduleFinally(new Command() { + public void execute() { doServerSideOrdering(); + VNotification.bringNotificationsToFront(); } }); } @@ -275,6 +278,7 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, orderingDefered = false; VWindow[] array = windowOrder.toArray(new VWindow[windowOrder.size()]); Arrays.sort(array, new Comparator<VWindow>() { + public int compare(VWindow o1, VWindow o2) { /* * Order by modality, then by bringtofront sequence. diff --git a/src/com/vaadin/terminal/gwt/client/ui/window/WindowConnector.java b/src/com/vaadin/terminal/gwt/client/ui/window/WindowConnector.java index 3a37baafbb..83de039f0b 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/window/WindowConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ui/window/WindowConnector.java @@ -201,8 +201,8 @@ public class WindowConnector extends AbstractComponentContainerConnector // We always have 1 child, unless the child is hidden Widget newChildWidget = null; ComponentConnector newChild = null; - if (getChildren().size() == 1) { - newChild = getChildren().get(0); + if (getChildComponents().size() == 1) { + newChild = getChildComponents().get(0); newChildWidget = newChild.getWidget(); } diff --git a/src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java b/src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java index 1acc9d128a..c2f887674a 100644 --- a/src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java +++ b/src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java @@ -64,10 +64,7 @@ import com.vaadin.ui.Root; public abstract class AbstractApplicationPortlet extends GenericPortlet implements Constants { - private static final Logger logger = Logger - .getLogger(AbstractApplicationPortlet.class.getName()); - - private static class WrappedHttpAndPortletRequest extends + public static class WrappedHttpAndPortletRequest extends WrappedPortletRequest { public WrappedHttpAndPortletRequest(PortletRequest request, @@ -112,7 +109,7 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet } } - private static class WrappedGateinRequest extends + public static class WrappedGateinRequest extends WrappedHttpAndPortletRequest { public WrappedGateinRequest(PortletRequest request, DeploymentConfiguration deploymentConfiguration) { @@ -134,7 +131,7 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet } } - private static class WrappedLiferayRequest extends + public static class WrappedLiferayRequest extends WrappedHttpAndPortletRequest { public WrappedLiferayRequest(PortletRequest request, @@ -169,7 +166,7 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet } - private static class AbstractApplicationPortletWrapper implements Callback { + public static class AbstractApplicationPortletWrapper implements Callback { private final AbstractApplicationPortlet portlet; @@ -203,6 +200,8 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet */ public static final String PORTAL_PARAMETER_VAADIN_THEME = "vaadin.theme"; + public static final String WRITE_AJAX_PAGE_SCRIPT_WIDGETSET_SHOULD_WRITE = "writeAjaxPageScriptWidgetsetShouldWrite"; + // TODO some parts could be shared with AbstractApplicationServlet // TODO Can we close the application when the portlet is removed? Do we know @@ -213,6 +212,7 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet private boolean productionMode = false; private DeploymentConfiguration deploymentConfiguration = new DeploymentConfiguration() { + public String getConfiguredWidgetset(WrappedRequest request) { String widgetset = getApplicationOrSystemProperty( @@ -271,6 +271,7 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet * @return The location of static resources (inside which there should * be a VAADIN directory). Does not end with a slash (/). */ + public String getStaticFileLocation(WrappedRequest request) { String staticFileLocation = WrappedPortletRequest.cast(request) .getPortalProperty( @@ -329,7 +330,7 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet * Print an information/warning message about running with xsrf * protection disabled */ - logger.warning(WARNING_XSRF_PROTECTION_DISABLED); + getLogger().warning(WARNING_XSRF_PROTECTION_DISABLED); } } @@ -345,7 +346,7 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet if (!productionMode) { /* Print an information/warning message about running in debug mode */ // TODO Maybe we need a different message for portlets? - logger.warning(NOT_PRODUCTION_MODE_INFO); + getLogger().warning(NOT_PRODUCTION_MODE_INFO); } } @@ -500,20 +501,7 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet AbstractApplicationPortletWrapper portletWrapper = new AbstractApplicationPortletWrapper( this); - WrappedPortletRequest wrappedRequest; - - String portalInfo = request.getPortalContext().getPortalInfo() - .toLowerCase(); - if (portalInfo.contains("liferay")) { - wrappedRequest = new WrappedLiferayRequest(request, - getDeploymentConfiguration()); - } else if (portalInfo.contains("gatein")) { - wrappedRequest = new WrappedGateinRequest(request, - getDeploymentConfiguration()); - } else { - wrappedRequest = new WrappedPortletRequest(request, - getDeploymentConfiguration()); - } + WrappedPortletRequest wrappedRequest = createWrappedRequest(request); WrappedPortletResponse wrappedResponse = new WrappedPortletResponse( response, getDeploymentConfiguration()); @@ -552,7 +540,7 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet if (application == null) { return; } - Application.setCurrentApplication(application); + Application.setCurrent(application); /* * Get or create an application context and an application @@ -650,7 +638,7 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet /* Handle the request */ if (requestType == RequestType.FILE_UPLOAD) { - applicationManager.handleFileUpload(wrappedRequest, + applicationManager.handleFileUpload(root, wrappedRequest, wrappedResponse); return; } else if (requestType == RequestType.BROWSER_DETAILS) { @@ -678,11 +666,12 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet } catch (final SessionExpiredException e) { // TODO Figure out a better way to deal with // SessionExpiredExceptions - logger.finest("A user session has expired"); + getLogger().finest("A user session has expired"); } catch (final GeneralSecurityException e) { // TODO Figure out a better way to deal with // GeneralSecurityExceptions - logger.fine("General security exception, the security key was probably incorrect."); + getLogger() + .fine("General security exception, the security key was probably incorrect."); } catch (final Throwable e) { handleServiceException(request, response, application, e); } finally { @@ -700,25 +689,51 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet } } finally { - Root.setCurrentRoot(null); - Application.setCurrentApplication(null); + Root.setCurrent(null); + Application.setCurrent(null); - requestTimer - .stop((AbstractWebApplicationContext) application - .getContext()); + PortletSession session = request + .getPortletSession(false); + if (session != null) { + requestTimer.stop(getApplicationContext(session)); + } } } } } } + /** + * Wraps the request in a (possibly portal specific) wrapped portlet + * request. + * + * @param request + * The original PortletRequest + * @return A wrapped version of the PorletRequest + */ + protected WrappedPortletRequest createWrappedRequest(PortletRequest request) { + String portalInfo = request.getPortalContext().getPortalInfo() + .toLowerCase(); + if (portalInfo.contains("liferay")) { + return new WrappedLiferayRequest(request, + getDeploymentConfiguration()); + } else if (portalInfo.contains("gatein")) { + return new WrappedGateinRequest(request, + getDeploymentConfiguration()); + } else { + return new WrappedPortletRequest(request, + getDeploymentConfiguration()); + } + + } + private DeploymentConfiguration getDeploymentConfiguration() { return deploymentConfiguration; } private void handleUnknownRequest(PortletRequest request, PortletResponse response) { - logger.warning("Unknown request type"); + getLogger().warning("Unknown request type"); } /** @@ -784,8 +799,9 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet os.write(buffer, 0, bytes); } } else { - logger.info("Requested resource [" + resourceID - + "] could not be found"); + getLogger().info( + "Requested resource [" + resourceID + + "] could not be found"); response.setProperty(ResourceResponse.HTTP_STATUS_CODE, Integer.toString(HttpServletResponse.SC_NOT_FOUND)); } @@ -1130,4 +1146,8 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet return PortletApplicationContext2.getApplicationContext(portletSession); } + private static final Logger getLogger() { + return Logger.getLogger(AbstractApplicationPortlet.class.getName()); + } + } diff --git a/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java b/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java index 905cfe7e3c..f7e46a7ca9 100644 --- a/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java +++ b/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java @@ -87,9 +87,6 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements // TODO Move some (all?) of the constants to a separate interface (shared // with portlet) - private static final Logger logger = Logger - .getLogger(AbstractApplicationServlet.class.getName()); - private Properties applicationProperties; private boolean productionMode = false; @@ -99,6 +96,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements private int resourceCacheTime = 3600; private DeploymentConfiguration deploymentConfiguration = new DeploymentConfiguration() { + public String getStaticFileLocation(WrappedRequest request) { HttpServletRequest servletRequest = WrappedHttpServletRequest .cast(request); @@ -149,8 +147,8 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements * if an exception has occurred that interferes with the * servlet's normal operation. */ - @SuppressWarnings("unchecked") @Override + @SuppressWarnings("unchecked") public void init(javax.servlet.ServletConfig servletConfig) throws javax.servlet.ServletException { super.init(servletConfig); @@ -186,7 +184,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements * Print an information/warning message about running with xsrf * protection disabled */ - logger.warning(WARNING_XSRF_PROTECTION_DISABLED); + getLogger().warning(WARNING_XSRF_PROTECTION_DISABLED); } } @@ -200,7 +198,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements if (!productionMode) { /* Print an information/warning message about running in debug mode */ - logger.warning(NOT_PRODUCTION_MODE_INFO); + getLogger().warning(NOT_PRODUCTION_MODE_INFO); } } @@ -214,7 +212,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements } catch (NumberFormatException nfe) { // Default is 1h resourceCacheTime = 3600; - logger.warning(WARNING_RESOURCE_CACHING_TIME_NOT_NUMERIC); + getLogger().warning(WARNING_RESOURCE_CACHING_TIME_NOT_NUMERIC); } } @@ -333,6 +331,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements * @throws IOException * if the request for the TRACE cannot be handled. */ + @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { @@ -386,7 +385,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements if (application == null) { return; } - Application.setCurrentApplication(application); + Application.setCurrent(application); /* * Get or create a WebApplicationContext and an ApplicationManager @@ -422,25 +421,29 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements /* Handle the request */ if (requestType == RequestType.FILE_UPLOAD) { - applicationManager.handleFileUpload(application, request, - response); + Root root = application.getRootForRequest(request); + if (root == null) { + throw new ServletException(ERROR_NO_ROOT_FOUND); + } + applicationManager.handleFileUpload(root, request, response); return; } else if (requestType == RequestType.UIDL) { - // Handles AJAX UIDL requests Root root = application.getRootForRequest(request); if (root == null) { - throw new ServletException(ERROR_NO_WINDOW_FOUND); + throw new ServletException(ERROR_NO_ROOT_FOUND); } + // Handles AJAX UIDL requests applicationManager.handleUidlRequest(request, response, servletWrapper, root); return; } else if (requestType == RequestType.BROWSER_DETAILS) { + // Browser details - not related to a specific root applicationManager.handleBrowserDetailsRequest(request, response, application); return; } - // Removes application if it has stopped (mayby by thread or + // Removes application if it has stopped (maybe by thread or // transactionlistener) if (!application.isRunning()) { endApplication(request, response, application); @@ -475,12 +478,13 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements .onRequestEnd(request, response); } } finally { - Root.setCurrentRoot(null); - Application.setCurrentApplication(null); + Root.setCurrent(null); + Application.setCurrent(null); - requestTimer - .stop((AbstractWebApplicationContext) application - .getContext()); + HttpSession session = request.getSession(false); + if (session != null) { + requestTimer.stop(getApplicationContext(session)); + } } } @@ -796,8 +800,8 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements resultPath = url.getFile(); } catch (final Exception e) { // FIXME: Handle exception - logger.log(Level.INFO, "Could not find resource path " + path, - e); + getLogger().log(Level.INFO, + "Could not find resource path " + path, e); } } return resultPath; @@ -1054,10 +1058,11 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements if (resourceUrl == null) { // cannot serve requested file - logger.info("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/VAADIN folder."); + getLogger() + .info("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/VAADIN folder."); response.setStatus(HttpServletResponse.SC_NOT_FOUND); return; } @@ -1065,9 +1070,10 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements // security check: do not permit navigation out of the VAADIN // directory if (!isAllowedVAADINResourceUrl(request, resourceUrl)) { - logger.info("Requested resource [" - + filename - + "] not accessible in the VAADIN directory or access to it is forbidden."); + getLogger() + .info("Requested resource [" + + filename + + "] not accessible in the VAADIN directory or access to it is forbidden."); response.setStatus(HttpServletResponse.SC_FORBIDDEN); return; } @@ -1090,10 +1096,10 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements } } catch (Exception e) { // Failed to find out last modified timestamp. Continue without it. - logger.log( - Level.FINEST, - "Failed to find out last modified timestamp. Continuing without it.", - e); + getLogger() + .log(Level.FINEST, + "Failed to find out last modified timestamp. Continuing without it.", + e); } finally { if (connection instanceof URLConnection) { try { @@ -1105,7 +1111,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements is.close(); } } catch (IOException e) { - logger.log(Level.INFO, + getLogger().log(Level.INFO, "Error closing URLConnection input stream", e); } } @@ -1130,7 +1136,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements * parameter in web.xml */ response.setHeader("Cache-Control", - "max-age: " + String.valueOf(resourceCacheTime)); + "max-age= " + String.valueOf(resourceCacheTime)); } // Write the resource to the client. @@ -1173,12 +1179,14 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements // loader sees it. if (!resourceUrl.getPath().contains("!/VAADIN/")) { - logger.info("Blocked attempt to access a JAR entry not starting with /VAADIN/: " - + resourceUrl); + getLogger().info( + "Blocked attempt to access a JAR entry not starting with /VAADIN/: " + + resourceUrl); return false; } - logger.fine("Accepted access to a JAR entry using a class loader: " - + resourceUrl); + getLogger().fine( + "Accepted access to a JAR entry using a class loader: " + + resourceUrl); return true; } else { // Some servers such as GlassFish extract files from JARs (file:) @@ -1188,11 +1196,13 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements // "/../" if (!resourceUrl.getPath().contains("/VAADIN/") || resourceUrl.getPath().contains("/../")) { - logger.info("Blocked attempt to access file: " + resourceUrl); + getLogger().info( + "Blocked attempt to access file: " + resourceUrl); return false; } - logger.fine("Accepted access to a file using a class loader: " - + resourceUrl); + getLogger().fine( + "Accepted access to a file using a class loader: " + + resourceUrl); return true; } } @@ -1733,4 +1743,8 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements c > 96 && c < 123 // a-z ; } + + private static final Logger getLogger() { + return Logger.getLogger(AbstractApplicationServlet.class.getName()); + } } diff --git a/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java b/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java index c08d70aa37..7cad8e3a33 100644 --- a/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java +++ b/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java @@ -49,6 +49,7 @@ import com.vaadin.Version; import com.vaadin.external.json.JSONArray; import com.vaadin.external.json.JSONException; import com.vaadin.external.json.JSONObject; +import com.vaadin.terminal.AbstractClientConnector; import com.vaadin.terminal.CombinedRequest; import com.vaadin.terminal.LegacyPaint; import com.vaadin.terminal.PaintException; @@ -67,14 +68,15 @@ import com.vaadin.terminal.gwt.client.ApplicationConnection; import com.vaadin.terminal.gwt.client.Connector; import com.vaadin.terminal.gwt.client.communication.MethodInvocation; import com.vaadin.terminal.gwt.client.communication.SharedState; +import com.vaadin.terminal.gwt.client.communication.UidlValue; import com.vaadin.terminal.gwt.server.BootstrapHandler.BootstrapContext; import com.vaadin.terminal.gwt.server.ComponentSizeValidator.InvalidLayout; +import com.vaadin.terminal.gwt.server.RpcManager.RpcInvocationException; import com.vaadin.ui.AbstractComponent; import com.vaadin.ui.AbstractField; import com.vaadin.ui.Component; -import com.vaadin.ui.DirtyConnectorTracker; +import com.vaadin.ui.ConnectorTracker; import com.vaadin.ui.HasComponents; -import com.vaadin.ui.Panel; import com.vaadin.ui.Root; import com.vaadin.ui.Window; @@ -91,9 +93,6 @@ public abstract class AbstractCommunicationManager implements Serializable { private static final String DASHDASH = "--"; - private static final Logger logger = Logger - .getLogger(AbstractCommunicationManager.class.getName()); - private static final RequestHandler APP_RESOURCE_HANDLER = new ApplicationResourceHandler(); private static final RequestHandler UNSUPPORTED_BROWSER_HANDLER = new UnsupportedBrowserHandler(); @@ -136,6 +135,9 @@ public abstract class AbstractCommunicationManager implements Serializable { private static final String GET_PARAM_ANALYZE_LAYOUTS = "analyzeLayouts"; + /** + * The application this communication manager is used for + */ private final Application application; private List<String> locales; @@ -519,7 +521,8 @@ public abstract class AbstractCommunicationManager implements Serializable { if (request.getParameter(GET_PARAM_HIGHLIGHT_COMPONENT) != null) { String pid = request .getParameter(GET_PARAM_HIGHLIGHT_COMPONENT); - highlightedConnector = root.getApplication().getConnector(pid); + highlightedConnector = root.getConnectorTracker().getConnector( + pid); highlightConnector(highlightedConnector); } } @@ -538,7 +541,7 @@ public abstract class AbstractCommunicationManager implements Serializable { if (root == null) { // This should not happen, no windows exists but // application is still open. - logger.warning("Could not get root for application"); + getLogger().warning("Could not get root for application"); return; } } else { @@ -561,7 +564,7 @@ public abstract class AbstractCommunicationManager implements Serializable { // FIXME: Handle exception // Not critical, but something is still wrong; print // stacktrace - logger.log(Level.WARNING, + getLogger().log(Level.WARNING, "getSystemMessages() failed - continuing", e2); } if (ci != null) { @@ -603,8 +606,9 @@ public abstract class AbstractCommunicationManager implements Serializable { } if (!Version.getFullVersion().equals(widgetsetVersion)) { - logger.warning(String.format(Constants.WIDGETSET_MISMATCH_INFO, - Version.getFullVersion(), widgetsetVersion)); + getLogger().warning( + String.format(Constants.WIDGETSET_MISMATCH_INFO, + Version.getFullVersion(), widgetsetVersion)); } } @@ -618,7 +622,7 @@ public abstract class AbstractCommunicationManager implements Serializable { protected void postPaint(Root root) { // Remove connectors that have been detached from the application during // handling of the request - root.getApplication().cleanConnectorMap(); + root.getConnectorTracker().cleanConnectorMap(); } protected void highlightConnector(Connector highlightedConnector) { @@ -637,7 +641,7 @@ public abstract class AbstractCommunicationManager implements Serializable { printHighlightedComponentHierarchy(sb, component); } - logger.info(sb.toString()); + getLogger().info(sb.toString()); } protected void printHighlightedComponentHierarchy(StringBuilder sb, @@ -764,12 +768,11 @@ public abstract class AbstractCommunicationManager implements Serializable { ArrayList<ClientConnector> dirtyVisibleConnectors = new ArrayList<ClientConnector>(); Application application = root.getApplication(); // Paints components - DirtyConnectorTracker rootConnectorTracker = root - .getDirtyConnectorTracker(); - logger.log(Level.FINE, "* Creating response to client"); + ConnectorTracker rootConnectorTracker = root.getConnectorTracker(); + getLogger().log(Level.FINE, "* Creating response to client"); if (repaintAll) { getClientCache(root).clear(); - rootConnectorTracker.markAllComponentsDirty(); + rootConnectorTracker.markAllConnectorsDirty(); // Reset sent locales locales = null; @@ -777,16 +780,18 @@ public abstract class AbstractCommunicationManager implements Serializable { } dirtyVisibleConnectors - .addAll(getDirtyVisibleComponents(rootConnectorTracker)); + .addAll(getDirtyVisibleConnectors(rootConnectorTracker)); - logger.log(Level.FINE, "Found " + dirtyVisibleConnectors.size() - + " dirty connectors to paint"); + getLogger().log( + Level.FINE, + "Found " + dirtyVisibleConnectors.size() + + " dirty connectors to paint"); for (ClientConnector connector : dirtyVisibleConnectors) { if (connector instanceof Component) { ((Component) connector).updateState(); } } - rootConnectorTracker.markAllComponentsClean(); + rootConnectorTracker.markAllConnectorsClean(); outWriter.print("\"changes\":["); @@ -840,16 +845,16 @@ public abstract class AbstractCommunicationManager implements Serializable { try { referenceState = stateType.newInstance(); } catch (Exception e) { - logger.log(Level.WARNING, + getLogger().log( + Level.WARNING, "Error creating reference object for state of type " + stateType.getName()); } } - JSONArray stateJsonArray = JsonCodec.encode(state, - referenceState, stateType, application); + Object stateJson = JsonCodec.encode(state, referenceState, + stateType, root.getConnectorTracker()); - sharedStates - .put(connector.getConnectorId(), stateJsonArray); + sharedStates.put(connector.getConnectorId(), stateJson); } catch (JSONException e) { throw new PaintException( "Failed to serialize shared state for connector " @@ -893,26 +898,24 @@ public abstract class AbstractCommunicationManager implements Serializable { outWriter.print("\"hierarchy\":"); JSONObject hierarchyInfo = new JSONObject(); - for (Connector connector : dirtyVisibleConnectors) { - if (connector instanceof HasComponents) { - HasComponents parent = (HasComponents) connector; - String parentConnectorId = parent.getConnectorId(); - JSONArray children = new JSONArray(); - - for (Component child : getChildComponents(parent)) { - if (isVisible(child)) { - children.put(child.getConnectorId()); - } - } - try { - hierarchyInfo.put(parentConnectorId, children); - } catch (JSONException e) { - throw new PaintException( - "Failed to send hierarchy information about " - + parentConnectorId + " to the client: " - + e.getMessage(), e); + for (ClientConnector connector : dirtyVisibleConnectors) { + String connectorId = connector.getConnectorId(); + JSONArray children = new JSONArray(); + + for (ClientConnector child : AbstractClientConnector + .getAllChildrenIterable(connector)) { + if (isVisible(child)) { + children.put(child.getConnectorId()); } } + try { + hierarchyInfo.put(connectorId, children); + } catch (JSONException e) { + throw new PaintException( + "Failed to send hierarchy information about " + + connectorId + " to the client: " + + e.getMessage(), e); + } } outWriter.append(hierarchyInfo.toString()); outWriter.print(", "); // close hierarchy @@ -937,7 +940,7 @@ public abstract class AbstractCommunicationManager implements Serializable { invocationJson.put(invocation.getMethodName()); JSONArray paramJson = new JSONArray(); for (int i = 0; i < invocation.getParameterTypes().length; ++i) { - Class<?> parameterType = invocation.getParameterTypes()[i]; + Type parameterType = invocation.getParameterTypes()[i]; Object referenceParameter = null; // TODO Use default values for RPC parameter types // if (!JsonCodec.isInternalType(parameterType)) { @@ -951,7 +954,7 @@ public abstract class AbstractCommunicationManager implements Serializable { // } paramJson.put(JsonCodec.encode( invocation.getParameters()[i], referenceParameter, - parameterType, application)); + parameterType, root.getConnectorTracker())); } invocationJson.put(paramJson); rpcCalls.put(invocationJson); @@ -1008,16 +1011,16 @@ public abstract class AbstractCommunicationManager implements Serializable { (Class[]) null); ci = (Application.SystemMessages) m.invoke(null, (Object[]) null); } catch (NoSuchMethodException e) { - logger.log(Level.WARNING, + getLogger().log(Level.WARNING, "getSystemMessages() failed - continuing", e); } catch (IllegalArgumentException e) { - logger.log(Level.WARNING, + getLogger().log(Level.WARNING, "getSystemMessages() failed - continuing", e); } catch (IllegalAccessException e) { - logger.log(Level.WARNING, + getLogger().log(Level.WARNING, "getSystemMessages() failed - continuing", e); } catch (InvocationTargetException e) { - logger.log(Level.WARNING, + getLogger().log(Level.WARNING, "getSystemMessages() failed - continuing", e); } @@ -1056,8 +1059,8 @@ public abstract class AbstractCommunicationManager implements Serializable { is = getThemeResourceAsStream(root, getTheme(root), resource); } catch (final Exception e) { // FIXME: Handle exception - logger.log(Level.FINER, "Failed to get theme resource stream.", - e); + getLogger().log(Level.FINER, + "Failed to get theme resource stream.", e); } if (is != null) { @@ -1076,13 +1079,13 @@ public abstract class AbstractCommunicationManager implements Serializable { r.close(); } catch (final java.io.IOException e) { // FIXME: Handle exception - logger.log(Level.INFO, "Resource transfer failed", e); + getLogger().log(Level.INFO, "Resource transfer failed", e); } outWriter.print("\"" + JsonPaintTarget.escapeJSON(layout.toString()) + "\""); } else { // FIXME: Handle exception - logger.severe("CustomLayout not found: " + resource); + getLogger().severe("CustomLayout not found: " + resource); } } outWriter.print("}"); @@ -1173,8 +1176,9 @@ public abstract class AbstractCommunicationManager implements Serializable { } sortByHierarchy((List) legacyComponents); for (Vaadin6Component c : legacyComponents) { - logger.fine("Painting Vaadin6Component " + c.getClass().getName() - + "@" + Integer.toHexString(c.hashCode())); + getLogger().fine( + "Painting Vaadin6Component " + c.getClass().getName() + "@" + + Integer.toHexString(c.hashCode())); paintTarget.startTag("change"); final String pid = c.getConnectorId(); paintTarget.addAttribute("pid", pid); @@ -1189,6 +1193,7 @@ public abstract class AbstractCommunicationManager implements Serializable { // containers rely on that their updateFromUIDL method has been called // before children start calling e.g. updateCaption Collections.sort(paintables, new Comparator<Component>() { + public int compare(Component c1, Component c2) { int depth1 = 0; while (c1.getParent() != null) { @@ -1223,6 +1228,30 @@ public abstract class AbstractCommunicationManager implements Serializable { } /** + * Checks if the connector is visible in context. For Components, + * {@link #isVisible(Component)} is used. For other types of connectors, the + * contextual visibility of its first Component ancestor is used. If no + * Component ancestor is found, the connector is not visible. + * + * @param connector + * The connector to check + * @return <code>true</code> if the connector is visible to the client, + * <code>false</code> otherwise + */ + static boolean isVisible(ClientConnector connector) { + if (connector instanceof Component) { + return isVisible((Component) connector); + } else { + ClientConnector parent = connector.getParent(); + if (parent == null) { + return false; + } else { + return isVisible(parent); + } + } + } + + /** * Checks if the component is visible in context, i.e. returns false if the * child is hidden, the parent is hidden or the parent says the child should * not be rendered (using @@ -1233,9 +1262,17 @@ public abstract class AbstractCommunicationManager implements Serializable { * @return true if the child is visible to the client, false otherwise */ static boolean isVisible(Component child) { + if (!child.isVisible()) { + return false; + } + HasComponents parent = child.getParent(); - if (parent == null || !child.isVisible()) { - return child.isVisible(); + if (parent == null) { + if (child instanceof Root) { + return child.isVisible(); + } else { + return false; + } } return parent.isComponentVisible(child) && isVisible(parent); @@ -1256,30 +1293,6 @@ public abstract class AbstractCommunicationManager implements Serializable { } - public static Iterable<Component> getChildComponents(HasComponents cc) { - // TODO This must be moved to Root/Panel - if (cc instanceof Root) { - Root root = (Root) cc; - List<Component> children = new ArrayList<Component>(); - if (root.getContent() != null) { - children.add(root.getContent()); - } - for (Window w : root.getWindows()) { - children.add(w); - } - return children; - } else if (cc instanceof Panel) { - // This is so wrong.. (#2924) - if (((Panel) cc).getContent() == null) { - return Collections.emptyList(); - } else { - return Collections.singleton((Component) ((Panel) cc) - .getContent()); - } - } - return cc; - } - /** * Collects all pending RPC calls from listed {@link ClientConnector}s and * clears their RPC queues. @@ -1402,7 +1415,7 @@ public abstract class AbstractCommunicationManager implements Serializable { for (int bi = 1; bi < bursts.length; bi++) { // unescape any encoded separator characters in the burst final String burst = unescapeBurst(bursts[bi]); - success &= handleBurst(request, application2, burst); + success &= handleBurst(request, root, burst); // In case that there were multiple bursts, we know that this is // a special synchronous case for closing window. Thus we are @@ -1444,36 +1457,38 @@ public abstract class AbstractCommunicationManager implements Serializable { * directly. * * @param source - * @param app - * application receiving the burst + * @param root + * the root receiving the burst * @param burst * the content of the burst as a String to be parsed * @return true if the processing of the burst was successful and there were * no messages to non-existent components */ - public boolean handleBurst(Object source, Application app, + public boolean handleBurst(WrappedRequest source, Root root, final String burst) { boolean success = true; try { Set<Connector> enabledConnectors = new HashSet<Connector>(); - List<MethodInvocation> invocations = parseInvocations(burst); + List<MethodInvocation> invocations = parseInvocations( + root.getConnectorTracker(), burst); for (MethodInvocation invocation : invocations) { - final ClientConnector connector = getConnector(app, + final ClientConnector connector = getConnector(root, invocation.getConnectorId()); if (connector != null && connector.isConnectorEnabled()) { enabledConnectors.add(connector); } } + for (int i = 0; i < invocations.size(); i++) { MethodInvocation invocation = invocations.get(i); - final ClientConnector connector = getConnector(app, + final ClientConnector connector = getConnector(root, invocation.getConnectorId()); if (connector == null) { - logger.log( + getLogger().log( Level.WARNING, "RPC call to " + invocation.getInterfaceName() + "." + invocation.getMethodName() @@ -1511,13 +1526,23 @@ public abstract class AbstractCommunicationManager implements Serializable { msg += ", caption=" + caption; } } - logger.warning(msg); + getLogger().warning(msg); continue; } if (invocation instanceof ServerRpcMethodInvocation) { - ServerRpcManager.applyInvocation(connector, - (ServerRpcMethodInvocation) invocation); + try { + ServerRpcManager.applyInvocation(connector, + (ServerRpcMethodInvocation) invocation); + } catch (RpcInvocationException e) { + Throwable realException = e.getCause(); + Component errorComponent = null; + if (connector instanceof Component) { + errorComponent = (Component) connector; + } + handleChangeVariablesError(root.getApplication(), + errorComponent, realException, null); + } } else { // All code below is for legacy variable changes @@ -1525,8 +1550,18 @@ public abstract class AbstractCommunicationManager implements Serializable { Map<String, Object> changes = legacyInvocation .getVariableChanges(); try { - changeVariables(source, (VariableOwner) connector, - changes); + if (connector instanceof VariableOwner) { + changeVariables(source, (VariableOwner) connector, + changes); + } else { + throw new IllegalStateException( + "Received legacy variable change for " + + connector.getClass().getName() + + " (" + + connector.getConnectorId() + + ") which is not a VariableOwner. The client-side connector sent these legacy varaibles: " + + changes.keySet()); + } } catch (Exception e) { Component errorComponent = null; if (connector instanceof Component) { @@ -1537,16 +1572,15 @@ public abstract class AbstractCommunicationManager implements Serializable { errorComponent = (Component) dropHandlerOwner; } } - handleChangeVariablesError(app, errorComponent, e, - changes); - + handleChangeVariablesError(root.getApplication(), + errorComponent, e, changes); } } } - } catch (JSONException e) { - logger.warning("Unable to parse RPC call from the client: " - + e.getMessage()); + getLogger().warning( + "Unable to parse RPC call from the client: " + + e.getMessage()); // TODO or return success = false? throw new RuntimeException(e); } @@ -1558,12 +1592,15 @@ public abstract class AbstractCommunicationManager implements Serializable { * Parse a message burst from the client into a list of MethodInvocation * instances. * + * @param connectorTracker + * The ConnectorTracker used to lookup connectors * @param burst * message string (JSON) * @return list of MethodInvocation to perform * @throws JSONException */ - private List<MethodInvocation> parseInvocations(final String burst) + private List<MethodInvocation> parseInvocations( + ConnectorTracker connectorTracker, final String burst) throws JSONException { JSONArray invocationsJson = new JSONArray(burst); @@ -1576,7 +1613,7 @@ public abstract class AbstractCommunicationManager implements Serializable { JSONArray invocationJson = invocationsJson.getJSONArray(i); MethodInvocation invocation = parseInvocation(invocationJson, - previousInvocation); + previousInvocation, connectorTracker); if (invocation != null) { // Can be null iff the invocation was a legacy invocation and it // was merged with the previous one @@ -1588,7 +1625,8 @@ public abstract class AbstractCommunicationManager implements Serializable { } private MethodInvocation parseInvocation(JSONArray invocationJson, - MethodInvocation previousInvocation) throws JSONException { + MethodInvocation previousInvocation, + ConnectorTracker connectorTracker) throws JSONException { String connectorId = invocationJson.getString(0); String interfaceName = invocationJson.getString(1); String methodName = invocationJson.getString(2); @@ -1604,10 +1642,10 @@ public abstract class AbstractCommunicationManager implements Serializable { return parseLegacyChangeVariablesInvocation(connectorId, interfaceName, methodName, (LegacyChangeVariablesInvocation) previousInvocation, - parametersJson); + parametersJson, connectorTracker); } else { return parseServerRpcInvocation(connectorId, interfaceName, - methodName, parametersJson); + methodName, parametersJson, connectorTracker); } } @@ -1615,17 +1653,18 @@ public abstract class AbstractCommunicationManager implements Serializable { private LegacyChangeVariablesInvocation parseLegacyChangeVariablesInvocation( String connectorId, String interfaceName, String methodName, LegacyChangeVariablesInvocation previousInvocation, - JSONArray parametersJson) throws JSONException { + JSONArray parametersJson, ConnectorTracker connectorTracker) + throws JSONException { if (parametersJson.length() != 2) { throw new JSONException( "Invalid parameters in legacy change variables call. Expected 2, was " + parametersJson.length()); } - String variableName = (String) JsonCodec - .decodeInternalType(String.class, true, - parametersJson.getJSONArray(0), application); - Object value = JsonCodec.decodeInternalType( - parametersJson.getJSONArray(1), application); + String variableName = parametersJson.getString(0); + UidlValue uidlValue = (UidlValue) JsonCodec.decodeInternalType( + UidlValue.class, true, parametersJson.get(1), connectorTracker); + + Object value = uidlValue.getValue(); if (previousInvocation != null && previousInvocation.getConnectorId().equals(connectorId)) { @@ -1639,7 +1678,8 @@ public abstract class AbstractCommunicationManager implements Serializable { private ServerRpcMethodInvocation parseServerRpcInvocation( String connectorId, String interfaceName, String methodName, - JSONArray parametersJson) throws JSONException { + JSONArray parametersJson, ConnectorTracker connectorTracker) + throws JSONException { ServerRpcMethodInvocation invocation = new ServerRpcMethodInvocation( connectorId, interfaceName, methodName, parametersJson.length()); @@ -1648,10 +1688,10 @@ public abstract class AbstractCommunicationManager implements Serializable { .getGenericParameterTypes(); for (int j = 0; j < parametersJson.length(); ++j) { - JSONArray parameterJson = parametersJson.getJSONArray(j); + Object parameterValue = parametersJson.get(j); Type parameterType = declaredRpcMethodParameterTypes[j]; parameters[j] = JsonCodec.decodeInternalOrCustomType(parameterType, - parameterJson, application); + parameterValue, connectorTracker); } invocation.setParameters(parameters); return invocation; @@ -1662,8 +1702,9 @@ public abstract class AbstractCommunicationManager implements Serializable { owner.changeVariables(source, m); } - protected ClientConnector getConnector(Application app, String connectorId) { - ClientConnector c = app.getConnector(connectorId); + protected ClientConnector getConnector(Root root, String connectorId) { + ClientConnector c = root.getConnectorTracker() + .getConnector(connectorId); if (c == null && connectorId.equals(getDragAndDropService().getConnectorId())) { return getDragAndDropService(); @@ -1745,10 +1786,10 @@ public abstract class AbstractCommunicationManager implements Serializable { * map from variable names to values */ private void handleChangeVariablesError(Application application, - Component owner, Exception e, Map<String, Object> m) { + Component owner, Throwable t, Map<String, Object> m) { boolean handled = false; ChangeVariablesErrorEvent errorEvent = new ChangeVariablesErrorEvent( - owner, e, m); + owner, t, m); if (owner instanceof AbstractField) { try { @@ -1889,8 +1930,9 @@ public abstract class AbstractCommunicationManager implements Serializable { DateFormat dateFormat = DateFormat.getDateTimeInstance( DateFormat.SHORT, DateFormat.SHORT, l); if (!(dateFormat instanceof SimpleDateFormat)) { - logger.warning("Unable to get default date pattern for locale " - + l.toString()); + getLogger().warning( + "Unable to get default date pattern for locale " + + l.toString()); dateFormat = new SimpleDateFormat(); } final String df = ((SimpleDateFormat) dateFormat).toPattern(); @@ -2016,16 +2058,16 @@ public abstract class AbstractCommunicationManager implements Serializable { * root window for which dirty components is to be fetched * @return */ - private ArrayList<Component> getDirtyVisibleComponents( - DirtyConnectorTracker dirtyConnectorTracker) { - ArrayList<Component> dirtyComponents = new ArrayList<Component>(); - for (Component c : dirtyConnectorTracker.getDirtyComponents()) { + private ArrayList<ClientConnector> getDirtyVisibleConnectors( + ConnectorTracker connectorTracker) { + ArrayList<ClientConnector> dirtyConnectors = new ArrayList<ClientConnector>(); + for (ClientConnector c : connectorTracker.getDirtyConnectors()) { if (isVisible(c)) { - dirtyComponents.add(c); + dirtyConnectors.add(c); } } - return dirtyComponents; + return dirtyConnectors; } /** @@ -2089,7 +2131,8 @@ public abstract class AbstractCommunicationManager implements Serializable { if (id == null) { id = nextTypeKey++; typeToKey.put(class1, id); - logger.log(Level.FINE, "Mapping " + class1.getName() + " to " + id); + getLogger().log(Level.FINE, + "Mapping " + class1.getName() + " to " + id); } return id.toString(); } @@ -2151,7 +2194,7 @@ public abstract class AbstractCommunicationManager implements Serializable { // if we do not yet have a currentRoot, it should be initialized // shortly, and we should send the initial UIDL - boolean sendUIDL = Root.getCurrentRoot() == null; + boolean sendUIDL = Root.getCurrent() == null; try { CombinedRequest combinedRequest = new CombinedRequest(request); @@ -2226,7 +2269,7 @@ public abstract class AbstractCommunicationManager implements Serializable { writeUidlResponse(request, true, pWriter, root, false); pWriter.print("}"); String initialUIDL = sWriter.toString(); - logger.log(Level.FINE, "Initial UIDL:" + initialUIDL); + getLogger().log(Level.FINE, "Initial UIDL:" + initialUIDL); return initialUIDL; } @@ -2380,4 +2423,7 @@ public abstract class AbstractCommunicationManager implements Serializable { } } + private static final Logger getLogger() { + return Logger.getLogger(AbstractCommunicationManager.class.getName()); + } } diff --git a/src/com/vaadin/terminal/gwt/server/AbstractWebApplicationContext.java b/src/com/vaadin/terminal/gwt/server/AbstractWebApplicationContext.java index c0ae0afc26..bf4ea860a8 100644 --- a/src/com/vaadin/terminal/gwt/server/AbstractWebApplicationContext.java +++ b/src/com/vaadin/terminal/gwt/server/AbstractWebApplicationContext.java @@ -32,9 +32,6 @@ import com.vaadin.terminal.ApplicationResource; public abstract class AbstractWebApplicationContext implements ApplicationContext, HttpSessionBindingListener, Serializable { - private static final Logger logger = Logger - .getLogger(AbstractWebApplicationContext.class.getName()); - protected Collection<TransactionListener> listeners = Collections .synchronizedList(new LinkedList<TransactionListener>()); @@ -145,7 +142,7 @@ public abstract class AbstractWebApplicationContext implements // remove same application here. Possible if you got e.g. session // lifetime 1 min but socket write may take longer than 1 min. // FIXME: Handle exception - logger.log(Level.SEVERE, + getLogger().log(Level.SEVERE, "Could not remove application, leaking memory.", e); } } @@ -252,4 +249,8 @@ public abstract class AbstractWebApplicationContext implements return lastRequestTime; } + private Logger getLogger() { + return Logger.getLogger(AbstractWebApplicationContext.class.getName()); + } + }
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/server/BootstrapHandler.java b/src/com/vaadin/terminal/gwt/server/BootstrapHandler.java index 8a0c700121..69f033c8cd 100644 --- a/src/com/vaadin/terminal/gwt/server/BootstrapHandler.java +++ b/src/com/vaadin/terminal/gwt/server/BootstrapHandler.java @@ -9,6 +9,10 @@ import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Serializable; import java.io.Writer; +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletResponse; @@ -16,6 +20,7 @@ import javax.servlet.http.HttpServletResponse; import com.vaadin.Application; import com.vaadin.RootRequiresMoreInformationException; import com.vaadin.Version; +import com.vaadin.annotations.LoadScripts; import com.vaadin.external.json.JSONException; import com.vaadin.external.json.JSONObject; import com.vaadin.terminal.DeploymentConfiguration; @@ -78,7 +83,7 @@ public abstract class BootstrapHandler implements RequestHandler { public Root getRoot() { if (!rootFetched) { - root = Root.getCurrentRoot(); + root = Root.getCurrent(); rootFetched = true; } return root; @@ -467,15 +472,15 @@ public abstract class BootstrapHandler implements RequestHandler { page.write("<meta http-equiv=\"X-UA-Compatible\" content=\"chrome=1\"/>\n"); page.write("<style type=\"text/css\">" - + "html, body {height:100%;margin:0;}</style>"); + + "html, body {height:100%;margin:0;}</style>\n"); // Add favicon links if (themeName != null) { String themeUri = getThemeUri(context, themeName); page.write("<link rel=\"shortcut icon\" type=\"image/vnd.microsoft.icon\" href=\"" - + themeUri + "/favicon.ico\" />"); + + themeUri + "/favicon.ico\" />\n"); page.write("<link rel=\"icon\" type=\"image/vnd.microsoft.icon\" href=\"" - + themeUri + "/favicon.ico\" />"); + + themeUri + "/favicon.ico\" />\n"); } Root root = context.getRoot(); @@ -484,7 +489,51 @@ public abstract class BootstrapHandler implements RequestHandler { page.write("<title>" + AbstractApplicationServlet.safeEscapeForHtml(title) - + "</title>"); + + "</title>\n"); + + if (root != null) { + List<LoadScripts> loadScriptsAnnotations = getAnnotationsFor( + root.getClass(), LoadScripts.class); + Collections.reverse(loadScriptsAnnotations); + // Begin from the end as a class might requests scripts that depend + // on script loaded by a super class + for (int i = loadScriptsAnnotations.size() - 1; i >= 0; i--) { + LoadScripts loadScripts = loadScriptsAnnotations.get(i); + String[] value = loadScripts.value(); + if (value != null) { + for (String script : value) { + page.write("<script type='text/javascript' src='"); + page.write(script); + page.write("'></script>\n"); + } + } + } + + } + } + + private static <T extends Annotation> List<T> getAnnotationsFor( + Class<?> type, Class<T> annotationType) { + List<T> list = new ArrayList<T>(); + // Find from the class hierarchy + Class<?> currentType = type; + while (currentType != Object.class) { + T annotation = currentType.getAnnotation(annotationType); + if (annotation != null) { + list.add(annotation); + } + currentType = currentType.getSuperclass(); + } + + // Find from an implemented interface + for (Class<?> iface : type.getInterfaces()) { + T annotation = iface.getAnnotation(annotationType); + if (annotation != null) { + list.add(annotation); + } + } + + return list; } /** diff --git a/src/com/vaadin/terminal/gwt/server/ClientConnector.java b/src/com/vaadin/terminal/gwt/server/ClientConnector.java index 7e74c26fb1..dfdd58879d 100644 --- a/src/com/vaadin/terminal/gwt/server/ClientConnector.java +++ b/src/com/vaadin/terminal/gwt/server/ClientConnector.java @@ -3,10 +3,15 @@ */ package com.vaadin.terminal.gwt.server; +import java.util.Collection; import java.util.List; +import com.vaadin.terminal.AbstractClientConnector; +import com.vaadin.terminal.Extension; import com.vaadin.terminal.gwt.client.Connector; import com.vaadin.terminal.gwt.client.communication.SharedState; +import com.vaadin.ui.Component; +import com.vaadin.ui.ComponentContainer; /** * Interface implemented by all connectors that are capable of communicating @@ -24,8 +29,6 @@ public interface ClientConnector extends Connector, RpcTarget { * * @return an unmodifiable ordered list of pending server to client method * calls (not null) - * - * @since 7.0 */ public List<ClientMethodInvocation> retrievePendingRpcCalls(); @@ -44,4 +47,93 @@ public interface ClientConnector extends Connector, RpcTarget { */ public Class<? extends SharedState> getStateType(); + public ClientConnector getParent(); + + /** + * Requests that the connector should be repainted as soon as possible. + */ + public void requestRepaint(); + + /** + * Causes a repaint of this connector, and all connectors below it. + * + * This should only be used in special cases, e.g when the state of a + * descendant depends on the state of an ancestor. + */ + public void requestRepaintAll(); + + /** + * Sets the parent connector of the connector. + * + * <p> + * This method automatically calls {@link #attach()} if the connector + * becomes attached to the application, regardless of whether it was + * attached previously. Conversely, if the parent is {@code null} and the + * connector is attached to the application, {@link #detach()} is called for + * the connector. + * </p> + * <p> + * This method is rarely called directly. One of the + * {@link ComponentContainer#addComponent(Component)} or + * {@link AbstractClientConnector#addExtension(Extension)} methods are + * normally used for adding connectors to a parent and they will call this + * method implicitly. + * </p> + * + * <p> + * It is not possible to change the parent without first setting the parent + * to {@code null}. + * </p> + * + * @param parent + * the parent connector + * @throws IllegalStateException + * if a parent is given even though the connector already has a + * parent + */ + public void setParent(ClientConnector parent); + + /** + * Notifies the connector that it is connected to an application. + * + * <p> + * The caller of this method is {@link #setParent(ClientConnector)} if the + * parent is itself already attached to the application. If not, the parent + * will call the {@link #attach()} for all its children when it is attached + * to the application. This method is always called before the connector's + * data is sent to the client-side for the first time. + * </p> + * + * <p> + * The attachment logic is implemented in {@link AbstractClientConnector}. + * </p> + */ + public void attach(); + + /** + * Notifies the component that it is detached from the application. + * + * <p> + * The caller of this method is {@link #setParent(ClientConnector)} if the + * parent is in the application. When the parent is detached from the + * application it is its response to call {@link #detach()} for all the + * children and to detach itself from the terminal. + * </p> + */ + public void detach(); + + /** + * Get a read-only collection of all extensions attached to this connector. + * + * @return a collection of extensions + */ + public Collection<Extension> getExtensions(); + + /** + * Remove an extension from this connector. + * + * @param extension + * the extension to remove. + */ + public void removeExtension(Extension extension); } diff --git a/src/com/vaadin/terminal/gwt/server/ClientMethodInvocation.java b/src/com/vaadin/terminal/gwt/server/ClientMethodInvocation.java index 99633a13d6..ad9484723b 100644 --- a/src/com/vaadin/terminal/gwt/server/ClientMethodInvocation.java +++ b/src/com/vaadin/terminal/gwt/server/ClientMethodInvocation.java @@ -6,6 +6,7 @@ package com.vaadin.terminal.gwt.server; import java.io.Serializable; import java.lang.reflect.Method; +import java.lang.reflect.Type; /** * Internal class for keeping track of pending server to client method @@ -19,7 +20,7 @@ public class ClientMethodInvocation implements Serializable, private final String interfaceName; private final String methodName; private final Object[] parameters; - private Class<?>[] parameterTypes; + private Type[] parameterTypes; // used for sorting calls between different connectors in the same Root private final long sequenceNumber; @@ -31,12 +32,12 @@ public class ClientMethodInvocation implements Serializable, this.connector = connector; this.interfaceName = interfaceName; methodName = method.getName(); - parameterTypes = method.getParameterTypes(); + parameterTypes = method.getGenericParameterTypes(); this.parameters = (null != parameters) ? parameters : new Object[0]; sequenceNumber = ++counter; } - public Class<?>[] getParameterTypes() { + public Type[] getParameterTypes() { return parameterTypes; } diff --git a/src/com/vaadin/terminal/gwt/server/CommunicationManager.java b/src/com/vaadin/terminal/gwt/server/CommunicationManager.java index 3dd2eb97fd..cc2981dc45 100644 --- a/src/com/vaadin/terminal/gwt/server/CommunicationManager.java +++ b/src/com/vaadin/terminal/gwt/server/CommunicationManager.java @@ -61,7 +61,8 @@ public class CommunicationManager extends AbstractCommunicationManager { /** * Handles file upload request submitted via Upload component. * - * @param application + * @param root + * The root for this request * * @see #getStreamVariableTargetUrl(ReceiverOwner, String, StreamVariable) * @@ -70,9 +71,9 @@ public class CommunicationManager extends AbstractCommunicationManager { * @throws IOException * @throws InvalidUIDLSecurityKeyException */ - public void handleFileUpload(Application application, - WrappedRequest request, WrappedResponse response) - throws IOException, InvalidUIDLSecurityKeyException { + public void handleFileUpload(Root root, WrappedRequest request, + WrappedResponse response) throws IOException, + InvalidUIDLSecurityKeyException { /* * URI pattern: APP/UPLOAD/[PID]/[NAME]/[SECKEY] See #createReceiverUrl @@ -86,14 +87,14 @@ public class CommunicationManager extends AbstractCommunicationManager { String uppUri = pathInfo.substring(startOfData); String[] parts = uppUri.split("/", 3); // 0 = pid, 1= name, 2 = sec key String variableName = parts[1]; - String paintableId = parts[0]; + String connectorId = parts[0]; StreamVariable streamVariable = pidToNameToStreamVariable.get( - paintableId).get(variableName); + connectorId).get(variableName); String secKey = streamVariableToSeckey.get(streamVariable); if (secKey.equals(parts[2])) { - Connector source = getConnector(application, paintableId); + Connector source = getConnector(root, connectorId); String contentType = request.getContentType(); if (contentType.contains("boundary")) { // Multipart requests contain boundary string @@ -117,13 +118,12 @@ public class CommunicationManager extends AbstractCommunicationManager { protected void postPaint(Root root) { super.postPaint(root); - Application application = root.getApplication(); if (pidToNameToStreamVariable != null) { Iterator<String> iterator = pidToNameToStreamVariable.keySet() .iterator(); while (iterator.hasNext()) { String connectorId = iterator.next(); - if (application.getConnector(connectorId) == null) { + if (root.getConnectorTracker().getConnector(connectorId) == null) { // Owner is no longer attached to the application Map<String, StreamVariable> removed = pidToNameToStreamVariable .get(connectorId); diff --git a/src/com/vaadin/terminal/gwt/server/ComponentSizeValidator.java b/src/com/vaadin/terminal/gwt/server/ComponentSizeValidator.java index 335067ca7a..171d440796 100644 --- a/src/com/vaadin/terminal/gwt/server/ComponentSizeValidator.java +++ b/src/com/vaadin/terminal/gwt/server/ComponentSizeValidator.java @@ -34,9 +34,6 @@ import com.vaadin.ui.Window; @SuppressWarnings({ "serial", "deprecation" }) public class ComponentSizeValidator implements Serializable { - private final static Logger logger = Logger - .getLogger(ComponentSizeValidator.class.getName()); - private final static int LAYERS_SHOWN = 4; /** @@ -134,7 +131,7 @@ public class ComponentSizeValidator implements Serializable { return parentCanDefineHeight(component); } catch (Exception e) { - logger.log(Level.FINER, + getLogger().log(Level.FINER, "An exception occurred while validating sizes.", e); return true; } @@ -154,7 +151,7 @@ public class ComponentSizeValidator implements Serializable { return parentCanDefineWidth(component); } catch (Exception e) { - logger.log(Level.FINER, + getLogger().log(Level.FINER, "An exception occurred while validating sizes.", e); return true; } @@ -653,11 +650,15 @@ public class ComponentSizeValidator implements Serializable { return; } catch (Exception e) { // TODO Auto-generated catch block - logger.log(Level.FINER, + getLogger().log(Level.FINER, "An exception occurred while validating sizes.", e); } } } + private static Logger getLogger() { + return Logger.getLogger(ComponentSizeValidator.class.getName()); + } + } diff --git a/src/com/vaadin/terminal/gwt/server/Constants.java b/src/com/vaadin/terminal/gwt/server/Constants.java index 7c467aa7f4..9e6b2c775b 100644 --- a/src/com/vaadin/terminal/gwt/server/Constants.java +++ b/src/com/vaadin/terminal/gwt/server/Constants.java @@ -68,7 +68,7 @@ public interface Constants { // Widget set parameter name static final String PARAMETER_WIDGETSET = "widgetset"; - static final String ERROR_NO_WINDOW_FOUND = "No window found. Did you remember to setMainWindow()?"; + static final String ERROR_NO_ROOT_FOUND = "Application did not return a root for the request and did not request extra information either. Something is wrong."; static final String DEFAULT_THEME_NAME = "reindeer"; diff --git a/src/com/vaadin/terminal/gwt/server/DragAndDropService.java b/src/com/vaadin/terminal/gwt/server/DragAndDropService.java index f6c96557ea..0e8d1c0152 100644 --- a/src/com/vaadin/terminal/gwt/server/DragAndDropService.java +++ b/src/com/vaadin/terminal/gwt/server/DragAndDropService.java @@ -4,6 +4,8 @@ package com.vaadin.terminal.gwt.server; import java.io.PrintWriter; +import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.logging.Logger; @@ -17,6 +19,7 @@ import com.vaadin.event.dd.DropTarget; import com.vaadin.event.dd.TargetDetails; import com.vaadin.event.dd.TargetDetailsImpl; import com.vaadin.event.dd.acceptcriteria.AcceptCriterion; +import com.vaadin.terminal.Extension; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.VariableOwner; import com.vaadin.terminal.gwt.client.communication.SharedState; @@ -26,9 +29,6 @@ import com.vaadin.ui.Component; public class DragAndDropService implements VariableOwner, ClientConnector { - private static final Logger logger = Logger - .getLogger(DragAndDropService.class.getName()); - private int lastVisitId; private boolean lastVisitAccepted = false; @@ -48,8 +48,9 @@ public class DragAndDropService implements VariableOwner, ClientConnector { // Validate drop handler owner if (!(owner instanceof DropTarget)) { - logger.severe("DropHandler owner " + owner - + " must implement DropTarget"); + getLogger() + .severe("DropHandler owner " + owner + + " must implement DropTarget"); return; } // owner cannot be null here @@ -79,8 +80,9 @@ public class DragAndDropService implements VariableOwner, ClientConnector { DropHandler dropHandler = (dropTarget).getDropHandler(); if (dropHandler == null) { // No dropHandler returned so no drop can be performed. - logger.fine("DropTarget.getDropHandler() returned null for owner: " - + dropTarget); + getLogger().fine( + "DropTarget.getDropHandler() returned null for owner: " + + dropTarget); return; } @@ -242,4 +244,47 @@ public class DragAndDropService implements VariableOwner, ClientConnector { public Class<? extends SharedState> getStateType() { return SharedState.class; } + + public void requestRepaint() { + // TODO Auto-generated method stub + + } + + public ClientConnector getParent() { + // TODO Auto-generated method stub + return null; + } + + public void requestRepaintAll() { + // TODO Auto-generated method stub + + } + + public void setParent(ClientConnector parent) { + // TODO Auto-generated method stub + + } + + public void attach() { + // TODO Auto-generated method stub + + } + + public void detach() { + // TODO Auto-generated method stub + + } + + public Collection<Extension> getExtensions() { + // TODO Auto-generated method stub + return Collections.emptySet(); + } + + public void removeExtension(Extension extension) { + // TODO Auto-generated method stub + } + + private Logger getLogger() { + return Logger.getLogger(DragAndDropService.class.getName()); + } } diff --git a/src/com/vaadin/terminal/gwt/server/GAEApplicationServlet.java b/src/com/vaadin/terminal/gwt/server/GAEApplicationServlet.java index 485c98f036..a6032fa98d 100644 --- a/src/com/vaadin/terminal/gwt/server/GAEApplicationServlet.java +++ b/src/com/vaadin/terminal/gwt/server/GAEApplicationServlet.java @@ -94,9 +94,6 @@ import com.vaadin.service.ApplicationContext; */ public class GAEApplicationServlet extends ApplicationServlet { - private static final Logger logger = Logger - .getLogger(GAEApplicationServlet.class.getName()); - // memcache mutex is MUTEX_BASE + sessio id private static final String MUTEX_BASE = "_vmutex"; @@ -209,8 +206,9 @@ public class GAEApplicationServlet extends ApplicationServlet { try { Thread.sleep(RETRY_AFTER_MILLISECONDS); } catch (InterruptedException e) { - logger.finer("Thread.sleep() interrupted while waiting for lock. Trying again. " - + e); + getLogger().finer( + "Thread.sleep() interrupted while waiting for lock. Trying again. " + + e); } } @@ -252,16 +250,16 @@ public class GAEApplicationServlet extends ApplicationServlet { ds.put(entity); } catch (DeadlineExceededException e) { - logger.warning("DeadlineExceeded for " + session.getId()); + getLogger().warning("DeadlineExceeded for " + session.getId()); sendDeadlineExceededNotification(request, response); } catch (NotSerializableException e) { - logger.log(Level.SEVERE, "Not serializable!", e); + getLogger().log(Level.SEVERE, "Not serializable!", e); // TODO this notification is usually not shown - should we redirect // in some other way - can we? sendNotSerializableNotification(request, response); } catch (Exception e) { - logger.log(Level.WARNING, + getLogger().log(Level.WARNING, "An exception occurred while servicing request.", e); sendCriticalErrorNotification(request, response); @@ -308,12 +306,14 @@ public class GAEApplicationServlet extends ApplicationServlet { session.setAttribute(WebApplicationContext.class.getName(), applicationContext); } catch (IOException e) { - logger.log(Level.WARNING, + getLogger().log( + Level.WARNING, "Could not de-serialize ApplicationContext for " + session.getId() + " A new one will be created. ", e); } catch (ClassNotFoundException e) { - logger.log(Level.WARNING, + getLogger().log( + Level.WARNING, "Could not de-serialize ApplicationContext for " + session.getId() + " A new one will be created. ", e); @@ -368,8 +368,9 @@ public class GAEApplicationServlet extends ApplicationServlet { List<Entity> entities = pq.asList(Builder .withLimit(CLEANUP_LIMIT)); if (entities != null) { - logger.info("Vaadin cleanup deleting " + entities.size() - + " expired Vaadin sessions."); + getLogger().info( + "Vaadin cleanup deleting " + entities.size() + + " expired Vaadin sessions."); List<Key> keys = new ArrayList<Key>(); for (Entity e : entities) { keys.add(e.getKey()); @@ -387,8 +388,9 @@ public class GAEApplicationServlet extends ApplicationServlet { List<Entity> entities = pq.asList(Builder .withLimit(CLEANUP_LIMIT)); if (entities != null) { - logger.info("Vaadin cleanup deleting " + entities.size() - + " expired appengine sessions."); + getLogger().info( + "Vaadin cleanup deleting " + entities.size() + + " expired appengine sessions."); List<Key> keys = new ArrayList<Key>(); for (Entity e : entities) { keys.add(e.getKey()); @@ -397,7 +399,11 @@ public class GAEApplicationServlet extends ApplicationServlet { } } } catch (Exception e) { - logger.log(Level.WARNING, "Exception while cleaning.", e); + getLogger().log(Level.WARNING, "Exception while cleaning.", e); } } + + private static final Logger getLogger() { + return Logger.getLogger(GAEApplicationServlet.class.getName()); + } } diff --git a/src/com/vaadin/terminal/gwt/server/JsonCodec.java b/src/com/vaadin/terminal/gwt/server/JsonCodec.java index e082eca47e..d3a2ef56f8 100644 --- a/src/com/vaadin/terminal/gwt/server/JsonCodec.java +++ b/src/com/vaadin/terminal/gwt/server/JsonCodec.java @@ -8,26 +8,30 @@ import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.io.Serializable; +import java.lang.reflect.Array; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; -import com.vaadin.Application; import com.vaadin.external.json.JSONArray; import com.vaadin.external.json.JSONException; import com.vaadin.external.json.JSONObject; import com.vaadin.terminal.gwt.client.Connector; import com.vaadin.terminal.gwt.client.communication.JsonEncoder; +import com.vaadin.terminal.gwt.client.communication.UidlValue; import com.vaadin.ui.Component; +import com.vaadin.ui.ConnectorTracker; /** * Decoder for converting RPC parameters and other values from JSON in transfer @@ -79,8 +83,16 @@ public class JsonCodec implements Serializable { public static boolean isInternalType(Type type) { if (type instanceof Class && ((Class<?>) type).isPrimitive()) { + if (type == byte.class || type == char.class) { + // Almost all primitive types are handled internally + return false; + } // All primitive types are handled internally return true; + } else if (type == UidlValue.class) { + // UidlValue is a special internal type wrapping type info and a + // value + return true; } return typeToTransportType.containsKey(getClassForType(type)); } @@ -93,65 +105,62 @@ public class JsonCodec implements Serializable { } } - public static String getTransportType(JSONArray encodedValue) - throws JSONException { - return encodedValue.getString(0); - } - private static Class<?> getType(String transportType) { return transportTypeToType.get(transportType); } - /** - * Decodes the given value and type, restricted to using only internal - * types. - * - * @param valueAndType - * @param application - * @throws JSONException - */ - @Deprecated - public static Object decodeInternalType(JSONArray valueAndType, - Application application) throws JSONException { - String transportType = getTransportType(valueAndType); - return decodeInternalType(getType(transportType), true, valueAndType, - application); - } - public static Object decodeInternalOrCustomType(Type targetType, - JSONArray valueAndType, Application application) + Object value, ConnectorTracker connectorTracker) throws JSONException { if (isInternalType(targetType)) { - return decodeInternalType(targetType, false, valueAndType, - application); + return decodeInternalType(targetType, false, value, + connectorTracker); } else { - return decodeCustomType(targetType, valueAndType, application); + return decodeCustomType(targetType, value, connectorTracker); } } - public static Object decodeCustomType(Type targetType, - JSONArray valueAndType, Application application) - throws JSONException { + public static Object decodeCustomType(Type targetType, Object value, + ConnectorTracker connectorTracker) throws JSONException { if (isInternalType(targetType)) { throw new JSONException("decodeCustomType cannot be used for " + targetType + ", which is an internal type"); } - String transportType = getCustomTransportType(getClassForType(targetType)); - String encodedTransportType = valueAndType.getString(0); - if (!transportTypesCompatible(encodedTransportType, transportType)) { - throw new JSONException("Expected a value of type " + transportType - + ", received " + encodedTransportType); - } // Try to decode object using fields - Object value = valueAndType.get(1); if (value == JSONObject.NULL) { return null; + } else if (targetType == byte.class || targetType == Byte.class) { + return Byte.valueOf(String.valueOf(value)); + } else if (targetType == char.class || targetType == Character.class) { + return Character.valueOf(String.valueOf(value).charAt(0)); + } else if (targetType instanceof Class<?> + && ((Class<?>) targetType).isArray()) { + // Legacy Object[] and String[] handled elsewhere, this takes care + // of generic arrays + return decodeArray((Class<?>) targetType, (JSONArray) value, + connectorTracker); + } else if (targetType == JSONObject.class + || targetType == JSONArray.class) { + return value; } else { - return decodeObject(targetType, (JSONObject) value, application); + return decodeObject(targetType, (JSONObject) value, + connectorTracker); } } + private static Object decodeArray(Class<?> targetType, JSONArray value, + ConnectorTracker connectorTracker) throws JSONException { + Class<?> componentType = targetType.getComponentType(); + Object array = Array.newInstance(componentType, value.length()); + for (int i = 0; i < value.length(); i++) { + Object decodedValue = decodeInternalOrCustomType(componentType, + value.get(i), connectorTracker); + Array.set(array, i, decodedValue); + } + return array; + } + /** * Decodes a value that is of an internal type. * <p> @@ -177,41 +186,41 @@ public class JsonCodec implements Serializable { * @throws JSONException */ public static Object decodeInternalType(Type targetType, - boolean restrictToInternalTypes, JSONArray valueAndType, - Application application) throws JSONException { - String encodedTransportType = valueAndType.getString(0); + boolean restrictToInternalTypes, Object encodedJsonValue, + ConnectorTracker connectorTracker) throws JSONException { if (!isInternalType(targetType)) { throw new JSONException("Type " + targetType + " is not a supported internal type."); } String transportType = getInternalTransportType(targetType); - if (!transportTypesCompatible(encodedTransportType, transportType)) { - throw new JSONException("Expected a value of type " + targetType - + ", received " + getType(encodedTransportType)); - } - - Object encodedJsonValue = valueAndType.get(1); - if (JsonEncoder.VTYPE_NULL.equals(encodedTransportType)) { + if (encodedJsonValue == JSONObject.NULL) { return null; } + + // UidlValue + if (targetType == UidlValue.class) { + return decodeUidlValue((JSONArray) encodedJsonValue, + connectorTracker); + } + // Collections if (JsonEncoder.VTYPE_LIST.equals(transportType)) { return decodeList(targetType, restrictToInternalTypes, - (JSONArray) encodedJsonValue, application); + (JSONArray) encodedJsonValue, connectorTracker); } else if (JsonEncoder.VTYPE_SET.equals(transportType)) { return decodeSet(targetType, restrictToInternalTypes, - (JSONArray) encodedJsonValue, application); + (JSONArray) encodedJsonValue, connectorTracker); } else if (JsonEncoder.VTYPE_MAP.equals(transportType)) { return decodeMap(targetType, restrictToInternalTypes, - (JSONObject) encodedJsonValue, application); + encodedJsonValue, connectorTracker); } // Arrays if (JsonEncoder.VTYPE_ARRAY.equals(transportType)) { return decodeObjectArray(targetType, (JSONArray) encodedJsonValue, - application); + connectorTracker); } else if (JsonEncoder.VTYPE_STRINGARRAY.equals(transportType)) { return decodeStringArray((JSONArray) encodedJsonValue); @@ -222,10 +231,10 @@ public class JsonCodec implements Serializable { String stringValue = String.valueOf(encodedJsonValue); if (JsonEncoder.VTYPE_CONNECTOR.equals(transportType)) { - return application.getConnector(stringValue); + return connectorTracker.getConnector(stringValue); } - // Standard Java types + // Legacy types if (JsonEncoder.VTYPE_STRING.equals(transportType)) { return stringValue; @@ -244,6 +253,15 @@ public class JsonCodec implements Serializable { throw new JSONException("Unknown type " + transportType); } + private static UidlValue decodeUidlValue(JSONArray encodedJsonValue, + ConnectorTracker connectorTracker) throws JSONException { + String type = encodedJsonValue.getString(0); + + Object decodedValue = decodeInternalType(getType(type), true, + encodedJsonValue.get(1), connectorTracker); + return new UidlValue(decodedValue); + } + private static boolean transportTypesCompatible( String encodedTransportType, String transportType) { if (encodedTransportType == null) { @@ -260,22 +278,94 @@ public class JsonCodec implements Serializable { } private static Map<Object, Object> decodeMap(Type targetType, - boolean restrictToInternalTypes, JSONObject jsonMap, - Application application) throws JSONException { - HashMap<Object, Object> map = new HashMap<Object, Object>(); - - Iterator<String> it = jsonMap.keys(); - while (it.hasNext()) { - String key = it.next(); - JSONArray encodedKey = new JSONArray(key); - JSONArray encodedValue = jsonMap.getJSONArray(key); - - Object decodedKey = decodeParametrizedType(targetType, - restrictToInternalTypes, 0, encodedKey, application); - Object decodedValue = decodeParametrizedType(targetType, - restrictToInternalTypes, 1, encodedValue, application); - map.put(decodedKey, decodedValue); + boolean restrictToInternalTypes, Object jsonMap, + ConnectorTracker connectorTracker) throws JSONException { + if (jsonMap instanceof JSONArray) { + // Client-side has no declared type information to determine + // encoding method for empty maps, so these are handled separately. + // See #8906. + JSONArray jsonArray = (JSONArray) jsonMap; + if (jsonArray.length() == 0) { + return new HashMap<Object, Object>(); + } } + + if (!restrictToInternalTypes && targetType instanceof ParameterizedType) { + Type keyType = ((ParameterizedType) targetType) + .getActualTypeArguments()[0]; + Type valueType = ((ParameterizedType) targetType) + .getActualTypeArguments()[1]; + if (keyType == String.class) { + return decodeStringMap(valueType, (JSONObject) jsonMap, + connectorTracker); + } else if (keyType == Connector.class) { + return decodeConnectorMap(valueType, (JSONObject) jsonMap, + connectorTracker); + } else { + return decodeObjectMap(keyType, valueType, (JSONArray) jsonMap, + connectorTracker); + } + } else { + return decodeStringMap(UidlValue.class, (JSONObject) jsonMap, + connectorTracker); + } + } + + private static Map<Object, Object> decodeObjectMap(Type keyType, + Type valueType, JSONArray jsonMap, ConnectorTracker connectorTracker) + throws JSONException { + Map<Object, Object> map = new HashMap<Object, Object>(); + + JSONArray keys = jsonMap.getJSONArray(0); + JSONArray values = jsonMap.getJSONArray(1); + + assert (keys.length() == values.length()); + + for (int i = 0; i < keys.length(); i++) { + Object key = decodeInternalOrCustomType(keyType, keys.get(i), + connectorTracker); + Object value = decodeInternalOrCustomType(valueType, values.get(i), + connectorTracker); + + map.put(key, value); + } + + return map; + } + + private static Map<Object, Object> decodeConnectorMap(Type valueType, + JSONObject jsonMap, ConnectorTracker connectorTracker) + throws JSONException { + Map<Object, Object> map = new HashMap<Object, Object>(); + + for (Iterator<?> iter = jsonMap.keys(); iter.hasNext();) { + String key = (String) iter.next(); + Object value = decodeInternalOrCustomType(valueType, + jsonMap.get(key), connectorTracker); + if (valueType == UidlValue.class) { + value = ((UidlValue) value).getValue(); + } + map.put(connectorTracker.getConnector(key), value); + } + + return map; + } + + private static Map<Object, Object> decodeStringMap(Type valueType, + JSONObject jsonMap, ConnectorTracker connectorTracker) + throws JSONException { + Map<Object, Object> map = new HashMap<Object, Object>(); + + for (Iterator<?> iter = jsonMap.keys(); iter.hasNext();) { + String key = (String) iter.next(); + Object value = decodeInternalOrCustomType(valueType, + jsonMap.get(key), connectorTracker); + if (valueType == UidlValue.class) { + value = ((UidlValue) value).getValue(); + } + map.put(key, value); + } + return map; } @@ -291,19 +381,20 @@ public class JsonCodec implements Serializable { * @throws JSONException */ private static Object decodeParametrizedType(Type targetType, - boolean restrictToInternalTypes, int typeIndex, - JSONArray encodedValueAndType, Application application) - throws JSONException { + boolean restrictToInternalTypes, int typeIndex, Object value, + ConnectorTracker connectorTracker) throws JSONException { if (!restrictToInternalTypes && targetType instanceof ParameterizedType) { Type childType = ((ParameterizedType) targetType) .getActualTypeArguments()[typeIndex]; // Only decode the given type - return decodeInternalOrCustomType(childType, encodedValueAndType, - application); + return decodeInternalOrCustomType(childType, value, + connectorTracker); } else { - // Only internal types when not enforcing a given type to avoid - // security issues - return decodeInternalType(encodedValueAndType, application); + // Only UidlValue when not enforcing a given type to avoid security + // issues + UidlValue decodeInternalType = (UidlValue) decodeInternalType( + UidlValue.class, true, value, connectorTracker); + return decodeInternalType.getValue(); } } @@ -323,21 +414,21 @@ public class JsonCodec implements Serializable { } private static Object[] decodeObjectArray(Type targetType, - JSONArray jsonArray, Application application) throws JSONException { - List list = decodeList(List.class, true, jsonArray, application); + JSONArray jsonArray, ConnectorTracker connectorTracker) + throws JSONException { + List list = decodeList(List.class, true, jsonArray, connectorTracker); return list.toArray(new Object[list.size()]); } private static List<Object> decodeList(Type targetType, boolean restrictToInternalTypes, JSONArray jsonArray, - Application application) throws JSONException { + ConnectorTracker connectorTracker) throws JSONException { List<Object> list = new ArrayList<Object>(); for (int i = 0; i < jsonArray.length(); ++i) { // each entry always has two elements: type and value - JSONArray encodedValueAndType = jsonArray.getJSONArray(i); + Object encodedValue = jsonArray.get(i); Object decodedChild = decodeParametrizedType(targetType, - restrictToInternalTypes, 0, encodedValueAndType, - application); + restrictToInternalTypes, 0, encodedValue, connectorTracker); list.add(decodedChild); } return list; @@ -345,10 +436,10 @@ public class JsonCodec implements Serializable { private static Set<Object> decodeSet(Type targetType, boolean restrictToInternalTypes, JSONArray jsonArray, - Application application) throws JSONException { + ConnectorTracker connectorTracker) throws JSONException { HashSet<Object> set = new HashSet<Object>(); - set.addAll(decodeList(List.class, restrictToInternalTypes, jsonArray, - application)); + set.addAll(decodeList(targetType, restrictToInternalTypes, jsonArray, + connectorTracker)); return set; } @@ -356,7 +447,7 @@ public class JsonCodec implements Serializable { * Returns the name that should be used as field name in the JSON. We strip * "set" from the setter, keeping the result - this is easy to do on both * server and client, avoiding some issues with cASE. E.g setZIndex() - * becomes "ZIndex". Also ensures that both getter and setter are present, + * becomes "zIndex". Also ensures that both getter and setter are present, * returning null otherwise. * * @param pd @@ -367,11 +458,14 @@ public class JsonCodec implements Serializable { if (pd.getReadMethod() == null || pd.getWriteMethod() == null) { return null; } - return pd.getWriteMethod().getName().substring(3); + String fieldName = pd.getWriteMethod().getName().substring(3); + fieldName = Character.toLowerCase(fieldName.charAt(0)) + + fieldName.substring(1); + return fieldName; } private static Object decodeObject(Type targetType, - JSONObject serializedObject, Application application) + JSONObject serializedObject, ConnectorTracker connectorTracker) throws JSONException { Class<?> targetClass = getClassForType(targetType); @@ -389,11 +483,10 @@ public class JsonCodec implements Serializable { if (fieldName == null) { continue; } - JSONArray encodedFieldValue = serializedObject - .getJSONArray(fieldName); + Object encodedFieldValue = serializedObject.get(fieldName); Type fieldType = pd.getReadMethod().getGenericReturnType(); Object decodedFieldValue = decodeInternalOrCustomType( - fieldType, encodedFieldValue, application); + fieldType, encodedFieldValue, connectorTracker); pd.getWriteMethod().invoke(decodedObject, decodedFieldValue); } @@ -412,55 +505,47 @@ public class JsonCodec implements Serializable { } } - @Deprecated - private static JSONArray encode(Object value, Application application) + public static Object encode(Object value, Object referenceValue, + Type valueType, ConnectorTracker connectorTracker) throws JSONException { - return encode(value, null, null, application); - } - public static JSONArray encode(Object value, Object referenceValue, - Type valueType, Application application) throws JSONException { + if (valueType == null) { + throw new IllegalArgumentException("type must be defined"); + } if (null == value) { return encodeNull(); } - if (valueType == null) { - valueType = value.getClass(); - } - - String internalTransportType = getInternalTransportType(valueType); if (value instanceof String[]) { String[] array = (String[]) value; JSONArray jsonArray = new JSONArray(); for (int i = 0; i < array.length; ++i) { jsonArray.put(array[i]); } - return combineTypeAndValue(JsonEncoder.VTYPE_STRINGARRAY, jsonArray); + return jsonArray; } else if (value instanceof String) { - return combineTypeAndValue(JsonEncoder.VTYPE_STRING, value); + return value; } else if (value instanceof Boolean) { - return combineTypeAndValue(JsonEncoder.VTYPE_BOOLEAN, value); + return value; } else if (value instanceof Number) { - return combineTypeAndValue(internalTransportType, value); + return value; + } else if (value instanceof Character) { + // Character is not a Number + return value; } else if (value instanceof Collection) { - if (internalTransportType == null) { - throw new RuntimeException( - "Unable to serialize unsupported type: " + valueType); - } Collection<?> collection = (Collection<?>) value; JSONArray jsonArray = encodeCollection(valueType, collection, - application); - - return combineTypeAndValue(internalTransportType, jsonArray); - } else if (value instanceof Object[]) { - Object[] array = (Object[]) value; - JSONArray jsonArray = encodeArrayContents(array, application); - return combineTypeAndValue(JsonEncoder.VTYPE_ARRAY, jsonArray); + connectorTracker); + return jsonArray; + } else if (valueType instanceof Class<?> + && ((Class<?>) valueType).isArray()) { + JSONArray jsonArray = encodeArrayContents(value, connectorTracker); + return jsonArray; } else if (value instanceof Map) { - JSONObject jsonMap = encodeMap(valueType, (Map<?, ?>) value, - application); - return combineTypeAndValue(JsonEncoder.VTYPE_MAP, jsonMap); + Object jsonMap = encodeMap(valueType, (Map<?, ?>) value, + connectorTracker); + return jsonMap; } else if (value instanceof Connector) { Connector connector = (Connector) value; if (value instanceof Component @@ -468,28 +553,24 @@ public class JsonCodec implements Serializable { .isVisible((Component) value))) { return encodeNull(); } - return combineTypeAndValue(JsonEncoder.VTYPE_CONNECTOR, - connector.getConnectorId()); - } else if (internalTransportType != null) { - return combineTypeAndValue(internalTransportType, - String.valueOf(value)); + return connector.getConnectorId(); } else if (value instanceof Enum) { - return encodeEnum((Enum) value, application); + return encodeEnum((Enum<?>) value, connectorTracker); + } else if (value instanceof JSONArray || value instanceof JSONObject) { + return value; } else { // Any object that we do not know how to encode we encode by looping // through fields - return combineTypeAndValue( - getCustomTransportType((Class<?>) valueType), - encodeObject(value, referenceValue, application)); + return encodeObject(value, referenceValue, connectorTracker); } } - private static JSONArray encodeNull() { - return combineTypeAndValue(JsonEncoder.VTYPE_NULL, JSONObject.NULL); + private static Object encodeNull() { + return JSONObject.NULL; } private static Object encodeObject(Object value, Object referenceValue, - Application application) throws JSONException { + ConnectorTracker connectorTracker) throws JSONException { JSONObject jsonMap = new JSONObject(); try { @@ -512,10 +593,18 @@ public class JsonCodec implements Serializable { equals = equals(fieldValue, referenceFieldValue); } if (!equals) { + if (jsonMap.has(fieldName)) { + throw new RuntimeException( + "Can't encode " + + value.getClass().getName() + + " as it has multiple fields with the name " + + fieldName.toLowerCase() + + ". This can happen if only casing distinguishes one property name from another."); + } jsonMap.put( fieldName, encode(fieldValue, referenceFieldValue, fieldType, - application)); + connectorTracker)); // } else { // System.out.println("Skipping field " + fieldName // + " of type " + fieldType.getName() @@ -549,45 +638,46 @@ public class JsonCodec implements Serializable { return false; } - private static JSONArray encodeEnum(Enum e, Application application) - throws JSONException { - String enumIdentifier = e.name(); - return combineTypeAndValue(e.getClass().getName(), enumIdentifier); + private static String encodeEnum(Enum<?> e, + ConnectorTracker connectorTracker) throws JSONException { + return e.name(); } - private static JSONArray encodeArrayContents(Object[] array, - Application application) throws JSONException { + private static JSONArray encodeArrayContents(Object array, + ConnectorTracker connectorTracker) throws JSONException { JSONArray jsonArray = new JSONArray(); - for (Object o : array) { - jsonArray.put(encode(o, null, null, application)); + Class<?> componentType = array.getClass().getComponentType(); + for (int i = 0; i < Array.getLength(array); i++) { + jsonArray.put(encode(Array.get(array, i), null, componentType, + connectorTracker)); } return jsonArray; } private static JSONArray encodeCollection(Type targetType, - Collection collection, Application application) + Collection collection, ConnectorTracker connectorTracker) throws JSONException { JSONArray jsonArray = new JSONArray(); for (Object o : collection) { - jsonArray.put(encodeChild(targetType, 0, o, application)); + jsonArray.put(encodeChild(targetType, 0, o, connectorTracker)); } return jsonArray; } - private static JSONArray encodeChild(Type targetType, int typeIndex, - Object o, Application application) throws JSONException { + private static Object encodeChild(Type targetType, int typeIndex, Object o, + ConnectorTracker connectorTracker) throws JSONException { if (targetType instanceof ParameterizedType) { Type childType = ((ParameterizedType) targetType) .getActualTypeArguments()[typeIndex]; // Encode using the given type - return encode(o, null, childType, application); + return encode(o, null, childType, connectorTracker); } else { - return encode(o, application); + throw new JSONException("Collection is missing generics"); } } - private static JSONObject encodeMap(Type mapType, Map<?, ?> map, - Application application) throws JSONException { + private static Object encodeMap(Type mapType, Map<?, ?> map, + ConnectorTracker connectorTracker) throws JSONException { Type keyType, valueType; if (mapType instanceof ParameterizedType) { @@ -597,26 +687,66 @@ public class JsonCodec implements Serializable { throw new JSONException("Map is missing generics"); } + if (map.isEmpty()) { + // Client -> server encodes empty map as an empty array because of + // #8906. Do the same for server -> client to maintain symmetry. + return new JSONArray(); + } + + if (keyType == String.class) { + return encodeStringMap(valueType, map, connectorTracker); + } else if (keyType == Connector.class) { + return encodeConnectorMap(valueType, map, connectorTracker); + } else { + return encodeObjectMap(keyType, valueType, map, connectorTracker); + } + } + + private static JSONArray encodeObjectMap(Type keyType, Type valueType, + Map<?, ?> map, ConnectorTracker connectorTracker) + throws JSONException { + JSONArray keys = new JSONArray(); + JSONArray values = new JSONArray(); + + for (Entry<?, ?> entry : map.entrySet()) { + Object encodedKey = encode(entry.getKey(), null, keyType, + connectorTracker); + Object encodedValue = encode(entry.getValue(), null, valueType, + connectorTracker); + + keys.put(encodedKey); + values.put(encodedValue); + } + + return new JSONArray(Arrays.asList(keys, values)); + } + + private static JSONObject encodeConnectorMap(Type valueType, Map<?, ?> map, + ConnectorTracker connectorTracker) throws JSONException { JSONObject jsonMap = new JSONObject(); - for (Object mapKey : map.keySet()) { - Object mapValue = map.get(mapKey); - JSONArray encodedKey = encode(mapKey, null, keyType, application); - JSONArray encodedValue = encode(mapValue, null, valueType, - application); - jsonMap.put(encodedKey.toString(), encodedValue); + + for (Entry<?, ?> entry : map.entrySet()) { + Connector key = (Connector) entry.getKey(); + Object encodedValue = encode(entry.getValue(), null, valueType, + connectorTracker); + jsonMap.put(key.getConnectorId(), encodedValue); } + return jsonMap; } - private static JSONArray combineTypeAndValue(String type, Object value) { - if (type == null) { - throw new RuntimeException("Type for value " + value - + " cannot be null!"); + private static JSONObject encodeStringMap(Type valueType, Map<?, ?> map, + ConnectorTracker connectorTracker) throws JSONException { + JSONObject jsonMap = new JSONObject(); + + for (Entry<?, ?> entry : map.entrySet()) { + String key = (String) entry.getKey(); + Object encodedValue = encode(entry.getValue(), null, valueType, + connectorTracker); + jsonMap.put(key, encodedValue); } - JSONArray outerArray = new JSONArray(); - outerArray.put(type); - outerArray.put(value); - return outerArray; + + return jsonMap; } /** diff --git a/src/com/vaadin/terminal/gwt/server/JsonPaintTarget.java b/src/com/vaadin/terminal/gwt/server/JsonPaintTarget.java index def334290e..70ab452e4e 100644 --- a/src/com/vaadin/terminal/gwt/server/JsonPaintTarget.java +++ b/src/com/vaadin/terminal/gwt/server/JsonPaintTarget.java @@ -26,6 +26,7 @@ import com.vaadin.terminal.ThemeResource; import com.vaadin.terminal.VariableOwner; import com.vaadin.terminal.gwt.client.Connector; import com.vaadin.ui.Alignment; +import com.vaadin.ui.Component; import com.vaadin.ui.CustomLayout; /** @@ -42,9 +43,6 @@ import com.vaadin.ui.CustomLayout; @SuppressWarnings("serial") public class JsonPaintTarget implements PaintTarget { - private static final Logger logger = Logger.getLogger(JsonPaintTarget.class - .getName()); - /* Document type declarations */ private final static String UIDL_ARG_NAME = "name"; @@ -161,6 +159,7 @@ public class JsonPaintTarget implements PaintTarget { * @throws Paintexception * if the paint operation failed. */ + public void endTag(String tagName) throws PaintException { // In case of null data output nothing: if (tagName == null) { @@ -327,6 +326,7 @@ public class JsonPaintTarget implements PaintTarget { * if the paint operation failed. * */ + public void addText(String str) throws PaintException { tag.addData("\"" + escapeJSON(str) + "\""); } @@ -399,7 +399,7 @@ public class JsonPaintTarget implements PaintTarget { } - public void addAttribute(String name, ClientConnector value) + public void addAttribute(String name, Component value) throws PaintException { final String id = value.getConnectorId(); addAttribute(name, id); @@ -467,8 +467,8 @@ public class JsonPaintTarget implements PaintTarget { tag.addVariable(new StringVariable(owner, name, escapeJSON(value))); } - public void addVariable(VariableOwner owner, String name, - ClientConnector value) throws PaintException { + public void addVariable(VariableOwner owner, String name, Component value) + throws PaintException { tag.addVariable(new StringVariable(owner, name, value.getConnectorId())); } @@ -515,6 +515,7 @@ public class JsonPaintTarget implements PaintTarget { * @throws PaintException * if the paint operation failed. */ + public void addUploadStreamVariable(VariableOwner owner, String name) throws PaintException { startTag("uploadstream"); @@ -534,6 +535,7 @@ public class JsonPaintTarget implements PaintTarget { * @throws PaintException * if the paint operation failed. */ + public void addSection(String sectionTagName, String sectionData) throws PaintException { tag.addData("{\"" + sectionTagName + "\":\"" + escapeJSON(sectionData) @@ -548,6 +550,7 @@ public class JsonPaintTarget implements PaintTarget { * @throws PaintException * if the paint operation failed. */ + public void addUIDL(String xml) throws PaintException { // Ensure that the target is open @@ -581,6 +584,7 @@ public class JsonPaintTarget implements PaintTarget { * @see com.vaadin.terminal.PaintTarget#addXMLSection(String, String, * String) */ + public void addXMLSection(String sectionTagName, String sectionData, String namespace) throws PaintException { @@ -645,12 +649,14 @@ public class JsonPaintTarget implements PaintTarget { * @see com.vaadin.terminal.PaintTarget#startPaintable(com.vaadin.terminal * .Paintable, java.lang.String) */ - public PaintStatus startPaintable(ClientConnector connector, String tagName) + + public PaintStatus startPaintable(Component connector, String tagName) throws PaintException { boolean topLevelPaintable = openPaintables.isEmpty(); - logger.fine("startPaintable for " + connector.getClass().getName() - + "@" + Integer.toHexString(connector.hashCode())); + getLogger().fine( + "startPaintable for " + connector.getClass().getName() + "@" + + Integer.toHexString(connector.hashCode())); startTag(tagName, true); openPaintables.push(connector); @@ -670,9 +676,10 @@ public class JsonPaintTarget implements PaintTarget { return PaintStatus.PAINTING; } - public void endPaintable(ClientConnector paintable) throws PaintException { - logger.fine("endPaintable for " + paintable.getClass().getName() + "@" - + Integer.toHexString(paintable.hashCode())); + public void endPaintable(Component paintable) throws PaintException { + getLogger().fine( + "endPaintable for " + paintable.getClass().getName() + "@" + + Integer.toHexString(paintable.hashCode())); ClientConnector openPaintable = openPaintables.peek(); if (paintable != openPaintable) { @@ -691,6 +698,7 @@ public class JsonPaintTarget implements PaintTarget { * * @see com.vaadin.terminal.PaintTarget#addCharacterData(java.lang.String ) */ + public void addCharacterData(String text) throws PaintException { if (text != null) { tag.addData(text); @@ -997,8 +1005,13 @@ public class JsonPaintTarget implements PaintTarget { * * @see com.vaadin.terminal.PaintTarget#isFullRepaint() */ + public boolean isFullRepaint() { return !cacheEnabled; } + private static final Logger getLogger() { + return Logger.getLogger(JsonPaintTarget.class.getName()); + } + } diff --git a/src/com/vaadin/terminal/gwt/server/PortletApplicationContext2.java b/src/com/vaadin/terminal/gwt/server/PortletApplicationContext2.java index 661da57af6..de4f918b75 100644 --- a/src/com/vaadin/terminal/gwt/server/PortletApplicationContext2.java +++ b/src/com/vaadin/terminal/gwt/server/PortletApplicationContext2.java @@ -48,9 +48,6 @@ import com.vaadin.ui.Root; @SuppressWarnings("serial") public class PortletApplicationContext2 extends AbstractWebApplicationContext { - private static final Logger logger = Logger - .getLogger(PortletApplicationContext2.class.getName()); - protected Map<Application, Set<PortletListener>> portletListeners = new HashMap<Application, Set<PortletListener>>(); protected transient PortletSession session; @@ -76,11 +73,11 @@ public class PortletApplicationContext2 extends AbstractWebApplicationContext { return new File(url.getFile()); } catch (final Exception e) { // FIXME: Handle exception - logger.log( - Level.INFO, - "Cannot access base directory, possible security issue " - + "with Application Server or Servlet Container", - e); + getLogger() + .log(Level.INFO, + "Cannot access base directory, possible security issue " + + "with Application Server or Servlet Container", + e); } } return null; @@ -419,4 +416,8 @@ public class PortletApplicationContext2 extends AbstractWebApplicationContext { "Portlet mode can only be changed from a portlet request"); } } + + private Logger getLogger() { + return Logger.getLogger(PortletApplicationContext2.class.getName()); + } } diff --git a/src/com/vaadin/terminal/gwt/server/PortletCommunicationManager.java b/src/com/vaadin/terminal/gwt/server/PortletCommunicationManager.java index 55f15da3d9..d3fbf4d988 100644 --- a/src/com/vaadin/terminal/gwt/server/PortletCommunicationManager.java +++ b/src/com/vaadin/terminal/gwt/server/PortletCommunicationManager.java @@ -43,12 +43,12 @@ public class PortletCommunicationManager extends AbstractCommunicationManager { super(application); } - public void handleFileUpload(WrappedRequest request, + public void handleFileUpload(Root root, WrappedRequest request, WrappedResponse response) throws IOException { String contentType = request.getContentType(); String name = request.getParameter("name"); String ownerId = request.getParameter("rec-owner"); - Connector owner = getConnector(getApplication(), ownerId); + Connector owner = getConnector(root, ownerId); StreamVariable streamVariable = ownerToNameToStreamVariable.get(owner) .get(name); @@ -73,7 +73,7 @@ public class PortletCommunicationManager extends AbstractCommunicationManager { .iterator(); while (iterator.hasNext()) { Connector owner = iterator.next(); - if (application.getConnector(owner.getConnectorId()) == null) { + if (getConnector(root, owner.getConnectorId()) == null) { // Owner is no longer attached to the application iterator.remove(); } diff --git a/src/com/vaadin/terminal/gwt/server/RpcManager.java b/src/com/vaadin/terminal/gwt/server/RpcManager.java index d240ab8467..026c847e2b 100644 --- a/src/com/vaadin/terminal/gwt/server/RpcManager.java +++ b/src/com/vaadin/terminal/gwt/server/RpcManager.java @@ -13,5 +13,36 @@ import java.io.Serializable; * @since 7.0 */ public interface RpcManager extends Serializable { - public void applyInvocation(ServerRpcMethodInvocation invocation); + public void applyInvocation(ServerRpcMethodInvocation invocation) + throws RpcInvocationException; + + /** + * Wrapper exception for exceptions which occur during invocation of an RPC + * call + * + * @author Vaadin Ltd + * @version @VERSION@ + * @since 7.0 + * + */ + public static class RpcInvocationException extends Exception { + + public RpcInvocationException() { + super(); + } + + public RpcInvocationException(String message, Throwable cause) { + super(message, cause); + } + + public RpcInvocationException(String message) { + super(message); + } + + public RpcInvocationException(Throwable cause) { + super(cause); + } + + } + } diff --git a/src/com/vaadin/terminal/gwt/server/ServerRpcManager.java b/src/com/vaadin/terminal/gwt/server/ServerRpcManager.java index 07f83864c2..d9931a9610 100644 --- a/src/com/vaadin/terminal/gwt/server/ServerRpcManager.java +++ b/src/com/vaadin/terminal/gwt/server/ServerRpcManager.java @@ -5,6 +5,7 @@ package com.vaadin.terminal.gwt.server; import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; @@ -66,9 +67,10 @@ public class ServerRpcManager<T> implements RpcManager { * non-null target of the RPC call * @param invocation * method invocation to perform + * @throws RpcInvocationException */ public static void applyInvocation(RpcTarget target, - ServerRpcMethodInvocation invocation) { + ServerRpcMethodInvocation invocation) throws RpcInvocationException { RpcManager manager = target.getRpcManager(invocation .getInterfaceClass()); if (manager != null) { @@ -109,7 +111,8 @@ public class ServerRpcManager<T> implements RpcManager { * @param invocation * method invocation to perform */ - public void applyInvocation(ServerRpcMethodInvocation invocation) { + public void applyInvocation(ServerRpcMethodInvocation invocation) + throws RpcInvocationException { Method method = invocation.getMethod(); Class<?>[] parameterTypes = method.getParameterTypes(); Object[] args = new Object[parameterTypes.length]; @@ -125,7 +128,7 @@ public class ServerRpcManager<T> implements RpcManager { try { method.invoke(implementation, args); } catch (Exception e) { - throw new RuntimeException("Unable to invoke method " + throw new RpcInvocationException("Unable to invoke method " + invocation.getMethodName() + " in " + invocation.getInterfaceName(), e); } diff --git a/src/com/vaadin/terminal/gwt/server/ServerRpcMethodInvocation.java b/src/com/vaadin/terminal/gwt/server/ServerRpcMethodInvocation.java index 6f278f7797..95565c4379 100644 --- a/src/com/vaadin/terminal/gwt/server/ServerRpcMethodInvocation.java +++ b/src/com/vaadin/terminal/gwt/server/ServerRpcMethodInvocation.java @@ -79,6 +79,12 @@ public class ServerRpcMethodInvocation extends MethodInvocation { } } + if (invocationMethod == null) { + throw new IllegalStateException("Can't find method " + methodName + + " with " + parameterCount + " parameters in " + + targetType.getName()); + } + return invocationMethod; } diff --git a/src/com/vaadin/terminal/gwt/server/WebBrowser.java b/src/com/vaadin/terminal/gwt/server/WebBrowser.java index 358f6f38fb..38b5409594 100644 --- a/src/com/vaadin/terminal/gwt/server/WebBrowser.java +++ b/src/com/vaadin/terminal/gwt/server/WebBrowser.java @@ -23,8 +23,6 @@ public class WebBrowser implements Terminal { private int screenHeight = 0; private int screenWidth = 0; - private int clientHeight = 0; - private int clientWidth = 0; private String browserApplication = null; private Locale locale; private String address; @@ -43,6 +41,7 @@ public class WebBrowser implements Terminal { * * @return Always returns null. */ + public String getDefaultTheme() { return null; } @@ -52,6 +51,7 @@ public class WebBrowser implements Terminal { * * @see com.vaadin.terminal.Terminal#getScreenHeight() */ + public int getScreenHeight() { return screenHeight; } @@ -61,35 +61,12 @@ public class WebBrowser implements Terminal { * * @see com.vaadin.terminal.Terminal#getScreenWidth() */ + public int getScreenWidth() { return screenWidth; } /** - * Gets the height of the client (browser window). - * <p> - * Note that the client size is only updated on a full repaint, not when the - * browser window size changes - * - * @return The height of the client or 0 if unknown. - */ - public int getClientHeight() { - return clientHeight; - } - - /** - * Gets the width of the client (browser window) - * <p> - * Note that the client size is only updated on a full repaint, not when the - * browser window size changes - * - * @return The width of the client or 0 if unknown. - */ - public int getClientWidth() { - return clientWidth; - } - - /** * Get the browser user-agent string. * * @return The raw browser userAgent string @@ -367,10 +344,6 @@ public class WebBrowser implements Terminal { * Screen width * @param sh * Screen height - * @param cw - * Client width - * @param ch - * Client height * @param tzo * TimeZone offset in minutes from GMT * @param rtzo @@ -383,9 +356,9 @@ public class WebBrowser implements Terminal { * the current date in milliseconds since the epoch * @param touchDevice */ - void updateClientSideDetails(String sw, String sh, String cw, String ch, - String tzo, String rtzo, String dstSavings, String dstInEffect, - String curDate, boolean touchDevice) { + void updateClientSideDetails(String sw, String sh, String tzo, String rtzo, + String dstSavings, String dstInEffect, String curDate, + boolean touchDevice) { if (sw != null) { try { screenHeight = Integer.parseInt(sh); @@ -394,14 +367,6 @@ public class WebBrowser implements Terminal { screenHeight = screenWidth = 0; } } - if (cw != null) { - try { - clientHeight = Integer.parseInt(ch); - clientWidth = Integer.parseInt(cw); - } catch (final NumberFormatException e) { - clientHeight = clientWidth = 0; - } - } if (tzo != null) { try { // browser->java conversion: min->ms, reverse sign @@ -462,8 +427,7 @@ public class WebBrowser implements Terminal { if (request.getParameter("sw") != null) { updateClientSideDetails(request.getParameter("sw"), - request.getParameter("sh"), request.getParameter("cw"), - request.getParameter("ch"), request.getParameter("tzo"), + request.getParameter("sh"), request.getParameter("tzo"), request.getParameter("rtzo"), request.getParameter("dstd"), request.getParameter("dston"), request.getParameter("curdate"), diff --git a/src/com/vaadin/terminal/gwt/server/WrappedHttpServletRequest.java b/src/com/vaadin/terminal/gwt/server/WrappedHttpServletRequest.java index b6f1a192cb..0774a79990 100644 --- a/src/com/vaadin/terminal/gwt/server/WrappedHttpServletRequest.java +++ b/src/com/vaadin/terminal/gwt/server/WrappedHttpServletRequest.java @@ -82,7 +82,7 @@ public class WrappedHttpServletRequest extends HttpServletRequestWrapper public WebBrowser getWebBrowser() { WebApplicationContext context = (WebApplicationContext) Application - .getCurrentApplication().getContext(); + .getCurrent().getContext(); return context.getBrowser(); } }; diff --git a/src/com/vaadin/terminal/gwt/server/WrappedPortletRequest.java b/src/com/vaadin/terminal/gwt/server/WrappedPortletRequest.java index 3838695aa3..85d8d5c69c 100644 --- a/src/com/vaadin/terminal/gwt/server/WrappedPortletRequest.java +++ b/src/com/vaadin/terminal/gwt/server/WrappedPortletRequest.java @@ -130,7 +130,7 @@ public class WrappedPortletRequest implements WrappedRequest { public WebBrowser getWebBrowser() { PortletApplicationContext2 context = (PortletApplicationContext2) Application - .getCurrentApplication().getContext(); + .getCurrent().getContext(); return context.getBrowser(); } }; diff --git a/src/com/vaadin/terminal/gwt/widgetsetutils/AcceptCriteriaFactoryGenerator.java b/src/com/vaadin/terminal/gwt/widgetsetutils/AcceptCriteriaFactoryGenerator.java index d8d3c23e0c..6d90a51761 100644 --- a/src/com/vaadin/terminal/gwt/widgetsetutils/AcceptCriteriaFactoryGenerator.java +++ b/src/com/vaadin/terminal/gwt/widgetsetutils/AcceptCriteriaFactoryGenerator.java @@ -4,7 +4,6 @@ package com.vaadin.terminal.gwt.widgetsetutils; import java.io.PrintWriter; -import java.util.Collection; import java.util.Date; import com.google.gwt.core.ext.Generator; @@ -16,14 +15,13 @@ import com.google.gwt.core.ext.typeinfo.JClassType; import com.google.gwt.core.ext.typeinfo.TypeOracle; import com.google.gwt.user.rebind.ClassSourceFileComposerFactory; import com.google.gwt.user.rebind.SourceWriter; -import com.vaadin.event.dd.acceptcriteria.AcceptCriterion; -import com.vaadin.event.dd.acceptcriteria.ClientCriterion; +import com.vaadin.terminal.gwt.client.ui.dd.AcceptCriterion; import com.vaadin.terminal.gwt.client.ui.dd.VAcceptCriterion; import com.vaadin.terminal.gwt.client.ui.dd.VAcceptCriterionFactory; /** * GWT generator to build {@link VAcceptCriterionFactory} implementation - * dynamically based on {@link ClientCriterion} annotations available in + * dynamically based on {@link AcceptCriterion} annotations available in * classpath. * */ @@ -102,26 +100,28 @@ public class AcceptCriteriaFactoryGenerator extends Generator { sourceWriter.println("name = name.intern();"); - Collection<Class<? extends AcceptCriterion>> clientSideVerifiableCriterion = ClassPathExplorer - .getCriterion(); - - for (Class<? extends AcceptCriterion> class1 : clientSideVerifiableCriterion) { - logger.log(Type.INFO, - "creating mapping for " + class1.getCanonicalName()); - String canonicalName = class1.getCanonicalName(); - Class<? extends VAcceptCriterion> clientClass = class1 - .getAnnotation(ClientCriterion.class).value(); - sourceWriter.print("if (\""); - sourceWriter.print(canonicalName); - sourceWriter.print("\" == name) return GWT.create("); - sourceWriter.print(clientClass.getCanonicalName()); - sourceWriter.println(".class );"); - sourceWriter.print("else "); + JClassType criteriaType = context.getTypeOracle().findType( + VAcceptCriterion.class.getName()); + for (JClassType clientClass : criteriaType.getSubtypes()) { + AcceptCriterion annotation = clientClass + .getAnnotation(AcceptCriterion.class); + if (annotation != null) { + String clientClassName = clientClass.getQualifiedSourceName(); + Class<?> serverClass = clientClass.getAnnotation( + AcceptCriterion.class).value(); + String serverClassName = serverClass.getCanonicalName(); + logger.log(Type.INFO, "creating mapping for " + serverClassName); + sourceWriter.print("if (\""); + sourceWriter.print(serverClassName); + sourceWriter.print("\" == name) return GWT.create("); + sourceWriter.print(clientClassName); + sourceWriter.println(".class );"); + sourceWriter.print("else "); + } } sourceWriter.println("return null;"); sourceWriter.outdent(); sourceWriter.println("}"); } - } diff --git a/src/com/vaadin/terminal/gwt/widgetsetutils/ClassPathExplorer.java b/src/com/vaadin/terminal/gwt/widgetsetutils/ClassPathExplorer.java index 6a0aa0f4c2..1c5b736492 100644 --- a/src/com/vaadin/terminal/gwt/widgetsetutils/ClassPathExplorer.java +++ b/src/com/vaadin/terminal/gwt/widgetsetutils/ClassPathExplorer.java @@ -6,33 +6,23 @@ package com.vaadin.terminal.gwt.widgetsetutils; import java.io.File; import java.io.FileFilter; import java.io.IOException; -import java.io.OutputStream; -import java.io.PrintStream; import java.net.JarURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; import java.util.ArrayList; -import java.util.Collection; -import java.util.Enumeration; import java.util.HashMap; -import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.jar.Attributes; -import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.Manifest; import java.util.logging.Level; import java.util.logging.Logger; -import com.vaadin.event.dd.acceptcriteria.AcceptCriterion; -import com.vaadin.event.dd.acceptcriteria.ClientCriterion; -import com.vaadin.terminal.gwt.server.ClientConnector; - /** * Utility class to collect widgetset related information from classpath. * Utility will seek all directories from classpaths, and jar files having @@ -53,9 +43,6 @@ import com.vaadin.terminal.gwt.server.ClientConnector; */ public class ClassPathExplorer { - private static Logger logger = Logger.getLogger(ClassPathExplorer.class - .getName()); - private static final String VAADIN_ADDON_VERSION_ATTRIBUTE = "Vaadin-Package-Version"; /** @@ -92,46 +79,6 @@ public class ClassPathExplorer { } /** - * Finds server side widgets with ClientWidget annotation on the class path - * (entries that can contain widgets/widgetsets - see - * getRawClasspathEntries()). - * - * As a side effect, also accept criteria are searched under the same class - * path entries and added into the acceptCriterion collection. - * - * @return a collection of {@link ClientConnector} classes - */ - public static void findAcceptCriteria() { - logger.info("Searching for accept criteria.."); - long start = System.currentTimeMillis(); - Set<String> keySet = classpathLocations.keySet(); - for (String url : keySet) { - logger.fine("Searching for accept criteria in " - + classpathLocations.get(url)); - searchForPaintables(classpathLocations.get(url), url); - } - long end = System.currentTimeMillis(); - - logger.info("Search took " + (end - start) + "ms"); - - } - - /** - * Finds all accept criteria having client side counterparts (classes with - * the {@link ClientCriterion} annotation). - * - * @return Collection of AcceptCriterion classes - */ - public static Collection<Class<? extends AcceptCriterion>> getCriterion() { - if (acceptCriterion.isEmpty()) { - // accept criterion are searched as a side effect, normally after - // paintable detection - findAcceptCriteria(); - } - return acceptCriterion; - } - - /** * Finds the names and locations of widgetsets available on the class path. * * @return map from widgetset classname to widgetset location URL @@ -154,6 +101,7 @@ public class ClassPathExplorer { sb.append(widgetsets.get(ws)); sb.append("\n"); } + final Logger logger = getLogger(); logger.info(sb.toString()); logger.info("Search took " + (end - start) + "ms"); return widgetsets; @@ -214,7 +162,7 @@ public class ClassPathExplorer { } catch (MalformedURLException e) { // should never happen as based on an existing URL, // only changing end of file name/path part - logger.log(Level.SEVERE, + getLogger().log(Level.SEVERE, "Error locating the widgetset " + classname, e); } } @@ -250,7 +198,7 @@ public class ClassPathExplorer { } } } catch (IOException e) { - logger.log(Level.WARNING, "Error parsing jar file", e); + getLogger().log(Level.WARNING, "Error parsing jar file", e); } } @@ -278,7 +226,7 @@ public class ClassPathExplorer { classpath = classpath.substring(0, classpath.length() - 1); } - logger.fine("Classpath: " + classpath); + getLogger().fine("Classpath: " + classpath); String[] split = classpath.split(pathSep); for (int i = 0; i < split.length; i++) { @@ -312,6 +260,7 @@ public class ClassPathExplorer { include(null, file, locations); } long end = System.currentTimeMillis(); + Logger logger = getLogger(); if (logger.isLoggable(Level.FINE)) { logger.fine("getClassPathLocations took " + (end - start) + "ms"); } @@ -352,7 +301,7 @@ public class ClassPathExplorer { url = new URL("jar:" + url.toExternalForm() + "!/"); JarURLConnection conn = (JarURLConnection) url .openConnection(); - logger.fine(url.toString()); + getLogger().fine(url.toString()); JarFile jarFile = conn.getJarFile(); Manifest manifest = jarFile.getManifest(); if (manifest != null) { @@ -363,9 +312,11 @@ public class ClassPathExplorer { } } } catch (MalformedURLException e) { - logger.log(Level.FINEST, "Failed to inspect JAR file", e); + getLogger().log(Level.FINEST, "Failed to inspect JAR file", + e); } catch (IOException e) { - logger.log(Level.FINEST, "Failed to inspect JAR file", e); + getLogger().log(Level.FINEST, "Failed to inspect JAR file", + e); } return false; @@ -445,151 +396,6 @@ public class ClassPathExplorer { } /** - * Searches for all paintable classes and accept criteria under a location - * based on {@link ClientCriterion} annotations. - * - * Note that client criteria are updated directly to the - * {@link #acceptCriterion} field, whereas paintables are added to the - * paintables map given as a parameter. - * - * @param location - * @param locationString - */ - private final static void searchForPaintables(URL location, - String locationString) { - - // Get a File object for the package - File directory = new File(location.getFile()); - - if (directory.exists() && !directory.isHidden()) { - // Get the list of the files contained in the directory - String[] files = directory.list(); - for (int i = 0; i < files.length; i++) { - // we are only interested in .class files - if (files[i].endsWith(".class")) { - // remove the .class extension - String classname = files[i].substring(0, - files[i].length() - 6); - String packageName = locationString - .substring(locationString.lastIndexOf("/") + 1); - classname = packageName + "." + classname; - tryToAdd(classname); - } - } - } else { - try { - // check files in jar file, entries will list all directories - // and files in jar - - URLConnection openConnection = location.openConnection(); - - if (openConnection instanceof JarURLConnection) { - JarURLConnection conn = (JarURLConnection) openConnection; - - JarFile jarFile = conn.getJarFile(); - - // Only scan for paintables in Vaadin add-ons - if (!isVaadinAddon(jarFile)) { - return; - } - - Enumeration<JarEntry> e = jarFile.entries(); - while (e.hasMoreElements()) { - JarEntry entry = e.nextElement(); - String entryname = entry.getName(); - if (!entry.isDirectory() - && entryname.endsWith(".class")) { - String classname = entryname.substring(0, - entryname.length() - 6); - if (classname.startsWith("/")) { - classname = classname.substring(1); - } - classname = classname.replace('/', '.'); - tryToAdd(classname); - } - } - } - } catch (IOException e) { - logger.warning(e.toString()); - } - } - - } - - /** - * A print stream that ignores all output. - * - * This is used to hide error messages from static initializers of classes - * being inspected. - */ - private static PrintStream devnull = new PrintStream(new OutputStream() { - @Override - public void write(int b) throws IOException { - // NOP - } - }); - - /** - * Collection of all {@link AcceptCriterion} classes, updated as a side - * effect of {@link #searchForPaintables(URL, String, Collection)} based on - * {@link ClientCriterion} annotations. - */ - private static Set<Class<? extends AcceptCriterion>> acceptCriterion = new HashSet<Class<? extends AcceptCriterion>>(); - - /** - * Checks a class for the {@link ClientCriterion} annotations, and adds it - * to the appropriate collection. - * - * @param fullclassName - */ - @SuppressWarnings("unchecked") - private static void tryToAdd(final String fullclassName) { - PrintStream out = System.out; - PrintStream err = System.err; - Throwable errorToShow = null; - Level logLevel = null; - try { - System.setErr(devnull); - System.setOut(devnull); - - Class<?> c = Class.forName(fullclassName); - - if (c.getAnnotation(ClientCriterion.class) != null) { - acceptCriterion.add((Class<? extends AcceptCriterion>) c); - } - } catch (UnsupportedClassVersionError e) { - // Inform the user about this as the class might contain a Paintable - // Typically happens when using an add-on that is compiled using a - // newer Java version. - logLevel = Level.INFO; - errorToShow = e; - } catch (ClassNotFoundException e) { - // Don't show to avoid flooding the user with irrelevant messages - logLevel = Level.FINE; - errorToShow = e; - } catch (LinkageError e) { - // Don't show to avoid flooding the user with irrelevant messages - logLevel = Level.FINE; - errorToShow = e; - } catch (Exception e) { - // Don't show to avoid flooding the user with irrelevant messages - logLevel = Level.FINE; - errorToShow = e; - } finally { - System.setErr(err); - System.setOut(out); - } - - // Must be done here after stderr and stdout have been reset. - if (errorToShow != null && logLevel != null) { - logger.log(logLevel, - "Failed to load class " + fullclassName + ". " - + errorToShow.getClass().getName() + ": " - + errorToShow.getMessage()); - } - } - - /** * Find and return the default source directory where to create new * widgetsets. * @@ -601,6 +407,9 @@ public class ClassPathExplorer { * @return URL */ public static URL getDefaultSourceDirectory() { + + final Logger logger = getLogger(); + if (logger.isLoggable(Level.FINE)) { logger.fine("classpathLocations values:"); ArrayList<String> locations = new ArrayList<String>( @@ -632,44 +441,21 @@ public class ClassPathExplorer { } /** - * Checks if the given jarFile is a Vaadin add-on. - * - * @param jarFile - * @return true if the file is an add-on, false otherwise - * @throws IOException - */ - private static boolean isVaadinAddon(JarFile jarFile) throws IOException { - Manifest manifest = jarFile.getManifest(); - if (manifest == null) { - return false; - } - Attributes mainAttributes = manifest.getMainAttributes(); - if (mainAttributes == null) { - return false; - } - - return (mainAttributes.getValue(VAADIN_ADDON_VERSION_ATTRIBUTE) != null); - } - - /** * Test method for helper tool */ public static void main(String[] args) { - ClassPathExplorer.findAcceptCriteria(); - logger.info("Found client criteria:"); - for (Class<? extends AcceptCriterion> cls : acceptCriterion) { - logger.info(cls.getCanonicalName()); - } - - logger.info(""); - logger.info("Searching available widgetsets..."); + getLogger().info("Searching available widgetsets..."); Map<String, URL> availableWidgetSets = ClassPathExplorer .getAvailableWidgetSets(); for (String string : availableWidgetSets.keySet()) { - logger.info(string + " in " + availableWidgetSets.get(string)); + getLogger().info(string + " in " + availableWidgetSets.get(string)); } } + private static final Logger getLogger() { + return Logger.getLogger(ClassPathExplorer.class.getName()); + } + } diff --git a/src/com/vaadin/terminal/gwt/widgetsetutils/CustomWidgetMapGenerator.java b/src/com/vaadin/terminal/gwt/widgetsetutils/CustomWidgetMapGenerator.java index f0d6f0453b..f0f3df20b0 100644 --- a/src/com/vaadin/terminal/gwt/widgetsetutils/CustomWidgetMapGenerator.java +++ b/src/com/vaadin/terminal/gwt/widgetsetutils/CustomWidgetMapGenerator.java @@ -7,6 +7,7 @@ import java.util.Collection; import java.util.HashSet; import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.ServerConnector; import com.vaadin.terminal.gwt.client.ui.Connect; import com.vaadin.terminal.gwt.client.ui.Connect.LoadStyle; @@ -25,8 +26,7 @@ public abstract class CustomWidgetMapGenerator extends WidgetMapGenerator { private Collection<Class<? extends ComponentConnector>> deferredPaintables = new HashSet<Class<? extends ComponentConnector>>(); @Override - protected LoadStyle getLoadStyle( - Class<? extends ComponentConnector> connector) { + protected LoadStyle getLoadStyle(Class<? extends ServerConnector> connector) { if (eagerPaintables == null) { init(); } diff --git a/src/com/vaadin/terminal/gwt/widgetsetutils/EagerWidgetMapGenerator.java b/src/com/vaadin/terminal/gwt/widgetsetutils/EagerWidgetMapGenerator.java index 8a1dfee3b5..084e1c3857 100644 --- a/src/com/vaadin/terminal/gwt/widgetsetutils/EagerWidgetMapGenerator.java +++ b/src/com/vaadin/terminal/gwt/widgetsetutils/EagerWidgetMapGenerator.java @@ -3,7 +3,7 @@ */ package com.vaadin.terminal.gwt.widgetsetutils; -import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.ServerConnector; import com.vaadin.terminal.gwt.client.ui.Connect.LoadStyle; /** @@ -23,8 +23,7 @@ import com.vaadin.terminal.gwt.client.ui.Connect.LoadStyle; public class EagerWidgetMapGenerator extends WidgetMapGenerator { @Override - protected LoadStyle getLoadStyle( - Class<? extends ComponentConnector> connector) { + protected LoadStyle getLoadStyle(Class<? extends ServerConnector> connector) { return LoadStyle.EAGER; } } diff --git a/src/com/vaadin/terminal/gwt/widgetsetutils/RpcManagerGenerator.java b/src/com/vaadin/terminal/gwt/widgetsetutils/GeneratedRpcMethodProviderGenerator.java index 2899061204..b1d69b178b 100644 --- a/src/com/vaadin/terminal/gwt/widgetsetutils/RpcManagerGenerator.java +++ b/src/com/vaadin/terminal/gwt/widgetsetutils/GeneratedRpcMethodProviderGenerator.java @@ -16,15 +16,15 @@ import com.google.gwt.core.ext.TreeLogger.Type; import com.google.gwt.core.ext.UnableToCompleteException; import com.google.gwt.core.ext.typeinfo.JClassType; import com.google.gwt.core.ext.typeinfo.JMethod; +import com.google.gwt.core.ext.typeinfo.JParameterizedType; import com.google.gwt.core.ext.typeinfo.JType; import com.google.gwt.core.ext.typeinfo.TypeOracle; import com.google.gwt.user.rebind.ClassSourceFileComposerFactory; import com.google.gwt.user.rebind.SourceWriter; -import com.vaadin.terminal.gwt.client.ServerConnector; -import com.vaadin.terminal.gwt.client.ConnectorMap; import com.vaadin.terminal.gwt.client.communication.ClientRpc; -import com.vaadin.terminal.gwt.client.communication.MethodInvocation; +import com.vaadin.terminal.gwt.client.communication.GeneratedRpcMethodProvider; import com.vaadin.terminal.gwt.client.communication.RpcManager; +import com.vaadin.terminal.gwt.client.communication.RpcMethod; /** * GWT generator that creates an implementation for {@link RpcManager} on the @@ -32,7 +32,7 @@ import com.vaadin.terminal.gwt.client.communication.RpcManager; * * @since 7.0 */ -public class RpcManagerGenerator extends Generator { +public class GeneratedRpcMethodProviderGenerator extends Generator { @Override public String generate(TreeLogger logger, GeneratorContext context, @@ -90,12 +90,24 @@ public class RpcManagerGenerator extends Generator { ClassSourceFileComposerFactory composer = null; composer = new ClassSourceFileComposerFactory(packageName, className); composer.addImport("com.google.gwt.core.client.GWT"); - composer.addImplementedInterface(RpcManager.class.getName()); + composer.addImport(RpcMethod.class.getName()); + composer.addImport(ClientRpc.class.getName()); + composer.addImport(com.vaadin.terminal.gwt.client.communication.Type.class + .getName()); + composer.addImplementedInterface(GeneratedRpcMethodProvider.class + .getName()); SourceWriter sourceWriter = composer.createSourceWriter(context, printWriter); sourceWriter.indent(); - List<JClassType> rpcInterfaces = new ArrayList<JClassType>(); + List<JMethod> rpcMethods = new ArrayList<JMethod>(); + + sourceWriter + .println("public java.util.Collection<RpcMethod> getGeneratedRpcMethods() {"); + sourceWriter.indent(); + + sourceWriter + .println("java.util.ArrayList<RpcMethod> list = new java.util.ArrayList<RpcMethod>();"); // iterate over RPC interfaces and create helper methods for each // interface @@ -104,81 +116,56 @@ public class RpcManagerGenerator extends Generator { // only interested in interfaces here, not implementations continue; } - rpcInterfaces.add(type); - // generate method to call methods of an RPC interface - sourceWriter.println("private void " + getInvokeMethodName(type) - + "(" + MethodInvocation.class.getName() + " invocation, " - + ConnectorMap.class.getName() + " connectorMap) {"); - sourceWriter.indent(); // loop over the methods of the interface and its superinterfaces // methods for (JClassType currentType : type.getFlattenedSupertypeHierarchy()) { for (JMethod method : currentType.getMethods()) { - sourceWriter.println("if (\"" + method.getName() - + "\".equals(invocation.getMethodName())) {"); - sourceWriter.indent(); - // construct parameter string with appropriate casts - String paramString = ""; + + // RpcMethod(String interfaceName, String methodName, + // Type... parameterTypes) + sourceWriter.print("list.add(new RpcMethod(\"" + + type.getQualifiedSourceName() + "\", \"" + + method.getName() + "\""); JType[] parameterTypes = method.getParameterTypes(); - for (int i = 0; i < parameterTypes.length; ++i) { - paramString = paramString + "(" - + parameterTypes[i].getQualifiedSourceName() - + ") invocation.getParameters()[" + i + "]"; - if (i < parameterTypes.length - 1) { - paramString = paramString + ", "; - } + for (JType parameter : parameterTypes) { + sourceWriter.print(", "); + writeTypeCreator(sourceWriter, parameter); } + sourceWriter.println(") {"); + sourceWriter.indent(); + sourceWriter - .println(ServerConnector.class.getName() - + " connector = connectorMap.getConnector(invocation.getConnectorId());"); - sourceWriter - .println("for (" - + ClientRpc.class.getName() - + " rpcImplementation : connector.getRpcImplementations(\"" - + type.getQualifiedSourceName() + "\")) {"); + .println("public void applyInvocation(ClientRpc target, Object... parameters) {"); sourceWriter.indent(); - sourceWriter.println("((" + type.getQualifiedSourceName() - + ") rpcImplementation)." + method.getName() + "(" - + paramString + ");"); + + sourceWriter.print("((" + type.getQualifiedSourceName() + + ")target)." + method.getName() + "("); + for (int i = 0; i < parameterTypes.length; i++) { + JType parameterType = parameterTypes[i]; + if (i != 0) { + sourceWriter.print(", "); + } + String parameterTypeName = getBoxedTypeName(parameterType); + sourceWriter.print("(" + parameterTypeName + + ") parameters[" + i + "]"); + } + sourceWriter.println(");"); + sourceWriter.outdent(); sourceWriter.println("}"); - sourceWriter.println("return;"); + sourceWriter.outdent(); - sourceWriter.println("}"); + sourceWriter.println("});"); } } - - sourceWriter.outdent(); - sourceWriter.println("}"); - - logger.log(Type.DEBUG, - "Constructed helper method for server to client RPC for " - + type.getName()); } - // generate top-level "switch-case" method to select the correct - // previously generated method based on the RPC interface - sourceWriter.println("public void applyInvocation(" - + MethodInvocation.class.getName() + " invocation, " - + ConnectorMap.class.getName() + " connectorMap) {"); - sourceWriter.indent(); + sourceWriter.println("return list;"); - for (JClassType type : rpcInterfaces) { - sourceWriter.println("if (\"" + type.getQualifiedSourceName() - + "\".equals(invocation.getInterfaceName())) {"); - sourceWriter.indent(); - sourceWriter.println(getInvokeMethodName(type) - + "(invocation, connectorMap);"); - sourceWriter.println("return;"); - sourceWriter.outdent(); - sourceWriter.println("}"); - - logger.log(Type.INFO, - "Configured server to client RPC for " + type.getName()); - } sourceWriter.outdent(); sourceWriter.println("}"); + sourceWriter.println(); // close generated class sourceWriter.outdent(); @@ -191,6 +178,33 @@ public class RpcManagerGenerator extends Generator { } + public static void writeTypeCreator(SourceWriter sourceWriter, JType type) { + String typeName = getBoxedTypeName(type); + sourceWriter.print("new Type(\"" + typeName + "\", "); + JParameterizedType parameterized = type.isParameterized(); + if (parameterized != null) { + sourceWriter.print("new Type[] {"); + JClassType[] typeArgs = parameterized.getTypeArgs(); + for (JClassType jClassType : typeArgs) { + writeTypeCreator(sourceWriter, jClassType); + sourceWriter.print(", "); + } + sourceWriter.print("}"); + } else { + sourceWriter.print("null"); + } + sourceWriter.print(")"); + } + + public static String getBoxedTypeName(JType type) { + if (type.isPrimitive() != null) { + // Used boxed types for primitives + return type.isPrimitive().getQualifiedBoxedSourceName(); + } else { + return type.getErasedType().getQualifiedSourceName(); + } + } + private String getInvokeMethodName(JClassType type) { return "invoke" + type.getQualifiedSourceName().replaceAll("\\.", "_"); } diff --git a/src/com/vaadin/terminal/gwt/widgetsetutils/LazyWidgetMapGenerator.java b/src/com/vaadin/terminal/gwt/widgetsetutils/LazyWidgetMapGenerator.java index 729a999a21..f8366beb46 100644 --- a/src/com/vaadin/terminal/gwt/widgetsetutils/LazyWidgetMapGenerator.java +++ b/src/com/vaadin/terminal/gwt/widgetsetutils/LazyWidgetMapGenerator.java @@ -3,7 +3,7 @@ */ package com.vaadin.terminal.gwt.widgetsetutils; -import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.ServerConnector; import com.vaadin.terminal.gwt.client.ui.Connect.LoadStyle; /** @@ -16,8 +16,7 @@ import com.vaadin.terminal.gwt.client.ui.Connect.LoadStyle; */ public class LazyWidgetMapGenerator extends WidgetMapGenerator { @Override - protected LoadStyle getLoadStyle( - Class<? extends ComponentConnector> connector) { + protected LoadStyle getLoadStyle(Class<? extends ServerConnector> connector) { return LoadStyle.LAZY; } diff --git a/src/com/vaadin/terminal/gwt/widgetsetutils/RpcProxyGenerator.java b/src/com/vaadin/terminal/gwt/widgetsetutils/RpcProxyGenerator.java index ad4e513049..d9bc8bc832 100644 --- a/src/com/vaadin/terminal/gwt/widgetsetutils/RpcProxyGenerator.java +++ b/src/com/vaadin/terminal/gwt/widgetsetutils/RpcProxyGenerator.java @@ -117,7 +117,7 @@ public class RpcProxyGenerator extends Generator { writer.println(" {"); writer.indent(); - writer.print("connector.getConnection().addMethodInvocationToQueue(new MethodInvocation(connector.getConnectorId(), \"" + writer.print("this.connector.getConnection().addMethodInvocationToQueue(new MethodInvocation(this.connector.getConnectorId(), \"" + requestedType.getQualifiedBinaryName() + "\", \""); writer.print(m.getName()); writer.print("\", new Object[] {"); diff --git a/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerGenerator.java b/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerGenerator.java index bc031f4bdb..1951f8ba40 100644 --- a/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerGenerator.java +++ b/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerGenerator.java @@ -6,7 +6,7 @@ package com.vaadin.terminal.gwt.widgetsetutils; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.Date; +import java.util.HashSet; import java.util.List; import com.google.gwt.core.client.GWT; @@ -15,13 +15,14 @@ import com.google.gwt.core.ext.GeneratorContext; import com.google.gwt.core.ext.TreeLogger; import com.google.gwt.core.ext.TreeLogger.Type; import com.google.gwt.core.ext.UnableToCompleteException; +import com.google.gwt.core.ext.typeinfo.JArrayType; import com.google.gwt.core.ext.typeinfo.JClassType; import com.google.gwt.core.ext.typeinfo.JEnumConstant; import com.google.gwt.core.ext.typeinfo.JEnumType; import com.google.gwt.core.ext.typeinfo.JMethod; import com.google.gwt.core.ext.typeinfo.JPrimitiveType; import com.google.gwt.core.ext.typeinfo.JType; -import com.google.gwt.core.ext.typeinfo.TypeOracle; +import com.google.gwt.core.ext.typeinfo.TypeOracleException; import com.google.gwt.json.client.JSONArray; import com.google.gwt.json.client.JSONObject; import com.google.gwt.json.client.JSONString; @@ -29,7 +30,7 @@ import com.google.gwt.json.client.JSONValue; import com.google.gwt.user.rebind.ClassSourceFileComposerFactory; import com.google.gwt.user.rebind.SourceWriter; import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.ConnectorMap; +import com.vaadin.terminal.gwt.client.communication.DiffJSONSerializer; import com.vaadin.terminal.gwt.client.communication.JSONSerializer; import com.vaadin.terminal.gwt.client.communication.JsonDecoder; import com.vaadin.terminal.gwt.client.communication.JsonEncoder; @@ -46,26 +47,32 @@ import com.vaadin.terminal.gwt.client.communication.SerializerMap; public class SerializerGenerator extends Generator { private static final String SUBTYPE_SEPARATOR = "___"; - private static String beanSerializerPackageName = SerializerMap.class + private static String serializerPackageName = SerializerMap.class .getPackage().getName(); @Override public String generate(TreeLogger logger, GeneratorContext context, - String beanTypeName) throws UnableToCompleteException { - JClassType beanType = context.getTypeOracle().findType(beanTypeName); - String beanSerializerClassName = getSerializerSimpleClassName(beanType); + String typeName) throws UnableToCompleteException { + JClassType type; + try { + type = (JClassType) context.getTypeOracle().parse(typeName); + } catch (TypeOracleException e1) { + logger.log(Type.ERROR, "Could not find type " + typeName, e1); + throw new UnableToCompleteException(); + } + String serializerClassName = getSerializerSimpleClassName(type); try { // Generate class source code - generateClass(logger, context, beanType, beanSerializerPackageName, - beanSerializerClassName); + generateClass(logger, context, type, serializerPackageName, + serializerClassName); } catch (Exception e) { logger.log(TreeLogger.ERROR, "SerializerGenerator failed for " - + beanType.getQualifiedSourceName(), e); + + type.getQualifiedSourceName(), e); throw new UnableToCompleteException(); } // return the fully qualifed name of the class generated - return getFullyQualifiedSerializerClassName(beanType); + return getFullyQualifiedSerializerClassName(type); } /** @@ -75,15 +82,16 @@ public class SerializerGenerator extends Generator { * Logger object * @param context * Generator context - * @param beanType + * @param type * @param beanTypeName * bean type for which the serializer is to be generated * @param beanSerializerTypeName * name of the serializer class to generate + * @throws UnableToCompleteException */ private void generateClass(TreeLogger logger, GeneratorContext context, - JClassType beanType, String serializerPackageName, - String serializerClassName) { + JClassType type, String serializerPackageName, + String serializerClassName) throws UnableToCompleteException { // get print writer that receives the source code PrintWriter printWriter = null; printWriter = context.tryCreate(logger, serializerPackageName, @@ -93,27 +101,33 @@ public class SerializerGenerator extends Generator { if (printWriter == null) { return; } - boolean isEnum = (beanType.isEnum() != null); + boolean isEnum = (type.isEnum() != null); + boolean isArray = (type.isArray() != null); - Date date = new Date(); - TypeOracle typeOracle = context.getTypeOracle(); - String beanQualifiedSourceName = beanType.getQualifiedSourceName(); + String qualifiedSourceName = type.getQualifiedSourceName(); logger.log(Type.DEBUG, "Processing serializable type " - + beanQualifiedSourceName + "..."); + + qualifiedSourceName + "..."); // init composer, set class properties, create source writer ClassSourceFileComposerFactory composer = null; composer = new ClassSourceFileComposerFactory(serializerPackageName, serializerClassName); composer.addImport(GWT.class.getName()); - composer.addImport(JSONArray.class.getName()); + composer.addImport(JSONValue.class.getName()); + composer.addImport(com.vaadin.terminal.gwt.client.communication.Type.class + .getName()); // composer.addImport(JSONObject.class.getName()); // composer.addImport(VPaintableMap.class.getName()); composer.addImport(JsonDecoder.class.getName()); // composer.addImport(VaadinSerializer.class.getName()); - composer.addImplementedInterface(JSONSerializer.class.getName() + "<" - + beanQualifiedSourceName + ">"); + if (isEnum || isArray) { + composer.addImplementedInterface(JSONSerializer.class.getName() + + "<" + qualifiedSourceName + ">"); + } else { + composer.addImplementedInterface(DiffJSONSerializer.class.getName() + + "<" + qualifiedSourceName + ">"); + } SourceWriter sourceWriter = composer.createSourceWriter(context, printWriter); @@ -121,49 +135,76 @@ public class SerializerGenerator extends Generator { // Serializer - // public JSONValue serialize(Object value, ConnectorMap idMapper, + // public JSONValue serialize(Object value, // ApplicationConnection connection) { sourceWriter.println("public " + JSONValue.class.getName() - + " serialize(" + beanQualifiedSourceName + " value, " - + ConnectorMap.class.getName() + " idMapper, " + + " serialize(" + qualifiedSourceName + " value, " + ApplicationConnection.class.getName() + " connection) {"); sourceWriter.indent(); // MouseEventDetails castedValue = (MouseEventDetails) value; - sourceWriter.println(beanQualifiedSourceName + " castedValue = (" - + beanQualifiedSourceName + ") value;"); + sourceWriter.println(qualifiedSourceName + " castedValue = (" + + qualifiedSourceName + ") value;"); if (isEnum) { - writeEnumSerializer(logger, sourceWriter, beanType); + writeEnumSerializer(logger, sourceWriter, type); + } else if (isArray) { + writeArraySerializer(logger, sourceWriter, type.isArray()); } else { - writeBeanSerializer(logger, sourceWriter, beanType); + writeBeanSerializer(logger, sourceWriter, type); } // } + sourceWriter.outdent(); sourceWriter.println("}"); + sourceWriter.println(); + + // Updater + // public void update(T target, Type type, JSONValue jsonValue, + // ApplicationConnection connection); + if (!isEnum && !isArray) { + sourceWriter.println("public void update(" + qualifiedSourceName + + " target, Type type, " + JSONValue.class.getName() + + " jsonValue, " + ApplicationConnection.class.getName() + + " connection) {"); + sourceWriter.indent(); + + writeBeanDeserializer(logger, sourceWriter, type); + + sourceWriter.outdent(); + sourceWriter.println("}"); + } // Deserializer - sourceWriter.println("public " + beanQualifiedSourceName - + " deserialize(" + JSONValue.class.getName() + " jsonValue, " - + beanQualifiedSourceName + " target, " - + ConnectorMap.class.getName() + " idMapper, " - + ApplicationConnection.class.getName() + " connection) {"); + // T deserialize(Type type, JSONValue jsonValue, ApplicationConnection + // connection); + sourceWriter.println("public " + qualifiedSourceName + + " deserialize(Type type, " + JSONValue.class.getName() + + " jsonValue, " + ApplicationConnection.class.getName() + + " connection) {"); sourceWriter.indent(); if (isEnum) { - writeEnumDeserializer(logger, sourceWriter, beanType.isEnum()); + writeEnumDeserializer(logger, sourceWriter, type.isEnum()); + } else if (isArray) { + writeArrayDeserializer(logger, sourceWriter, type.isArray()); } else { - writeBeanDeserializer(logger, sourceWriter, beanType); + sourceWriter.println(qualifiedSourceName + " target = GWT.create(" + + qualifiedSourceName + ".class);"); + sourceWriter + .println("update(target, type, jsonValue, connection);"); + // return target; + sourceWriter.println("return target;"); } - sourceWriter.println("}"); sourceWriter.outdent(); + sourceWriter.println("}"); // End of class - sourceWriter.println("}"); sourceWriter.outdent(); + sourceWriter.println("}"); // commit generated class context.commit(logger, printWriter); logger.log(TreeLogger.INFO, "Generated Serializer class " - + getFullyQualifiedSerializerClassName(beanType)); + + getFullyQualifiedSerializerClassName(type)); } private void writeEnumDeserializer(TreeLogger logger, @@ -182,27 +223,56 @@ public class SerializerGenerator extends Generator { sourceWriter.println("return null;"); } - private void writeBeanDeserializer(TreeLogger logger, - SourceWriter sourceWriter, JClassType beanType) { - String beanQualifiedSourceName = beanType.getQualifiedSourceName(); + private void writeArrayDeserializer(TreeLogger logger, + SourceWriter sourceWriter, JArrayType type) { + JType leafType = type.getLeafType(); + int rank = type.getRank(); + + sourceWriter.println(JSONArray.class.getName() + + " jsonArray = jsonValue.isArray();"); - // if (target == null) { - sourceWriter.println("if (target == null) {"); + // Type value = new Type[jsonArray.size()][][]; + sourceWriter.print(type.getQualifiedSourceName() + " value = new " + + leafType.getQualifiedSourceName() + "[jsonArray.size()]"); + for (int i = 1; i < rank; i++) { + sourceWriter.print("[]"); + } + sourceWriter.println(";"); + + sourceWriter.println("for(int i = 0 ; i < value.length; i++) {"); sourceWriter.indent(); - // target = GWT.create(VButtonState.class); - sourceWriter.println("target = GWT.create(" + beanQualifiedSourceName - + ".class);"); + JType componentType = type.getComponentType(); + + sourceWriter.print("value[i] = (" + + GeneratedRpcMethodProviderGenerator + .getBoxedTypeName(componentType) + ") " + + JsonDecoder.class.getName() + ".decodeValue("); + GeneratedRpcMethodProviderGenerator.writeTypeCreator(sourceWriter, + componentType); + sourceWriter.print(", jsonArray.get(i), null, connection)"); + + sourceWriter.println(";"); + sourceWriter.outdent(); sourceWriter.println("}"); + sourceWriter.println("return value;"); + } + + private void writeBeanDeserializer(TreeLogger logger, + SourceWriter sourceWriter, JClassType beanType) { + String beanQualifiedSourceName = beanType.getQualifiedSourceName(); + // JSONOBject json = (JSONObject)jsonValue; sourceWriter.println(JSONObject.class.getName() + " json = (" + JSONObject.class.getName() + ")jsonValue;"); for (JMethod method : getSetters(beanType)) { String setterName = method.getName(); - String fieldName = setterName.substring(3); // setZIndex() -> ZIndex + String baseName = setterName.substring(3); + String fieldName = getTransportFieldName(baseName); // setZIndex() + // -> zIndex JType setterParameterType = method.getParameterTypes()[0]; logger.log(Type.DEBUG, "* Processing field " + fieldName + " in " @@ -213,55 +283,41 @@ public class SerializerGenerator extends Generator { + "\")) {"); sourceWriter.indent(); String jsonFieldName = "json_" + fieldName; - // JSONArray json_Height = (JSONArray) json.get("height"); - sourceWriter.println("JSONArray " + jsonFieldName - + " = (JSONArray) json.get(\"" + fieldName + "\");"); + // JSONValue json_Height = json.get("height"); + sourceWriter.println("JSONValue " + jsonFieldName + + " = json.get(\"" + fieldName + "\");"); String fieldType; - String getterName = "get" + fieldName; + String getterName = "get" + baseName; JPrimitiveType primitiveType = setterParameterType.isPrimitive(); if (primitiveType != null) { // This is a primitive type -> must used the boxed type fieldType = primitiveType.getQualifiedBoxedSourceName(); if (primitiveType == JPrimitiveType.BOOLEAN) { - getterName = "is" + fieldName; + getterName = "is" + baseName; } } else { fieldType = setterParameterType.getQualifiedSourceName(); } - // String referenceValue; - sourceWriter.println(fieldType + " referenceValue;"); - // if (target == null) { - sourceWriter.println("if (target == null) {"); - sourceWriter.indent(); - // referenceValue = null; - sourceWriter.println("referenceValue = null;"); - // } else { - sourceWriter.println("} else {"); - // referenceValue = target.getHeight(); - sourceWriter.println("referenceValue = target." + getterName - + "();"); - // } - sourceWriter.outdent(); - sourceWriter.println("}"); + // String referenceValue = target.getHeight(); + sourceWriter.println(fieldType + " referenceValue = target." + + getterName + "();"); // target.setHeight((String) // JsonDecoder.decodeValue(jsonFieldValue,referenceValue, idMapper, // connection)); - sourceWriter.println("target." + setterName + "((" + fieldType - + ") " + JsonDecoder.class.getName() + ".decodeValue(" - + jsonFieldName - + ", referenceValue, idMapper, connection));"); + sourceWriter.print("target." + setterName + "((" + fieldType + ") " + + JsonDecoder.class.getName() + ".decodeValue("); + GeneratedRpcMethodProviderGenerator.writeTypeCreator(sourceWriter, + setterParameterType); + sourceWriter.println(", " + jsonFieldName + + ", referenceValue, connection));"); // } ... end of if contains - sourceWriter.println("}"); sourceWriter.outdent(); + sourceWriter.println("}"); } - - // return target; - sourceWriter.println("return target;"); - } private void writeEnumSerializer(TreeLogger logger, @@ -271,16 +327,47 @@ public class SerializerGenerator extends Generator { + "(castedValue.name());"); } + private void writeArraySerializer(TreeLogger logger, + SourceWriter sourceWriter, JArrayType array) { + sourceWriter.println(JSONArray.class.getName() + " values = new " + + JSONArray.class.getName() + "();"); + JType componentType = array.getComponentType(); + // JPrimitiveType primitive = componentType.isPrimitive(); + sourceWriter.println("for (int i = 0; i < castedValue.length; i++) {"); + sourceWriter.indent(); + sourceWriter.print("values.set(i, "); + sourceWriter.print(JsonEncoder.class.getName() + + ".encode(castedValue[i], false, connection)"); + sourceWriter.println(");"); + sourceWriter.outdent(); + sourceWriter.println("}"); + sourceWriter.println("return values;"); + } + private void writeBeanSerializer(TreeLogger logger, - SourceWriter sourceWriter, JClassType beanType) { + SourceWriter sourceWriter, JClassType beanType) + throws UnableToCompleteException { // JSONObject json = new JSONObject(); sourceWriter.println(JSONObject.class.getName() + " json = new " + JSONObject.class.getName() + "();"); + HashSet<String> usedFieldNames = new HashSet<String>(); + for (JMethod setterMethod : getSetters(beanType)) { String setterName = setterMethod.getName(); - String fieldName = setterName.substring(3); // setZIndex() -> ZIndex + String fieldName = getTransportFieldName(setterName.substring(3)); // setZIndex() + // -> zIndex + if (!usedFieldNames.add(fieldName)) { + logger.log( + TreeLogger.ERROR, + "Can't encode " + + beanType.getQualifiedSourceName() + + " as it has multiple fields with the name " + + fieldName.toLowerCase() + + ". This can happen if only casing distinguishes one property name from another."); + throw new UnableToCompleteException(); + } String getterName = findGetter(beanType, setterMethod); if (getterName == null) { @@ -292,13 +379,18 @@ public class SerializerGenerator extends Generator { // connection)); sourceWriter.println("json.put(\"" + fieldName + "\", " + JsonEncoder.class.getName() + ".encode(castedValue." - + getterName + "(), false, idMapper, connection));"); + + getterName + "(), false, connection));"); } // return json; sourceWriter.println("return json;"); } + private static String getTransportFieldName(String baseName) { + return Character.toLowerCase(baseName.charAt(0)) + + baseName.substring(1); + } + private String findGetter(JClassType beanType, JMethod setterMethod) { JType setterParameterType = setterMethod.getParameterTypes()[0]; String fieldName = setterMethod.getName().substring(3); @@ -344,10 +436,15 @@ public class SerializerGenerator extends Generator { return getSimpleClassName(beanType) + "_Serializer"; } - private static String getSimpleClassName(JClassType type) { - if (type.isMemberType()) { + private static String getSimpleClassName(JType type) { + JArrayType arrayType = type.isArray(); + if (arrayType != null) { + return "Array" + getSimpleClassName(arrayType.getComponentType()); + } + JClassType classType = type.isClass(); + if (classType != null && classType.isMemberType()) { // Assumed to be static sub class - String baseName = getSimpleClassName(type.getEnclosingType()); + String baseName = getSimpleClassName(classType.getEnclosingType()); String name = baseName + SUBTYPE_SEPARATOR + type.getSimpleSourceName(); return name; @@ -356,7 +453,6 @@ public class SerializerGenerator extends Generator { } public static String getFullyQualifiedSerializerClassName(JClassType type) { - return beanSerializerPackageName + "." - + getSerializerSimpleClassName(type); + return serializerPackageName + "." + getSerializerSimpleClassName(type); } } diff --git a/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerMapGenerator.java b/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerMapGenerator.java index 07efcda91b..5e151323a0 100644 --- a/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerMapGenerator.java +++ b/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerMapGenerator.java @@ -17,6 +17,7 @@ import com.google.gwt.core.ext.GeneratorContext; import com.google.gwt.core.ext.TreeLogger; import com.google.gwt.core.ext.TreeLogger.Type; import com.google.gwt.core.ext.UnableToCompleteException; +import com.google.gwt.core.ext.typeinfo.JArrayType; import com.google.gwt.core.ext.typeinfo.JClassType; import com.google.gwt.core.ext.typeinfo.JMethod; import com.google.gwt.core.ext.typeinfo.JParameterizedType; @@ -96,6 +97,10 @@ public class SerializerMapGenerator extends Generator { JClassType javaSerializable = typeOracle.findType(Serializable.class .getName()); for (JClassType type : typesNeedingSerializers) { + if (type.isArray() != null) { + // Don't check for arrays + continue; + } boolean serializable = type.isAssignableTo(javaSerializable); if (!serializable) { logger.log( @@ -166,8 +171,15 @@ public class SerializerMapGenerator extends Generator { // TODO cache serializer instances in a map for (JClassType type : typesNeedingSerializers) { - sourceWriter.println("if (type.equals(\"" - + type.getQualifiedBinaryName() + "\")) {"); + sourceWriter.print("if (type.equals(\"" + + type.getQualifiedSourceName() + "\")"); + if (type instanceof JArrayType) { + // Also add binary name to support encoding based on + // object.getClass().getName() + sourceWriter.print("||type.equals(\"" + type.getJNISignature() + + "\")"); + } + sourceWriter.println(") {"); sourceWriter.indent(); String serializerName = SerializerGenerator .getFullyQualifiedSerializerClassName(type); @@ -203,6 +215,7 @@ public class SerializerMapGenerator extends Generator { // Generate serializer classes for each subclass of SharedState JClassType serializerType = typeOracle.findType(SharedState.class .getName()); + types.add(serializerType); JClassType[] serializerSubtypes = serializerType.getSubtypes(); for (JClassType type : serializerSubtypes) { types.add(type); @@ -276,6 +289,14 @@ public class SerializerMapGenerator extends Generator { serializableTypes.add(typeClass); findSubTypesNeedingSerializers(typeClass, serializableTypes); } + + // Generate (n-1)-dimensional array serializer for n-dimensional array + JArrayType arrayType = type.isArray(); + if (arrayType != null) { + serializableTypes.add(arrayType); + addTypeIfNeeded(serializableTypes, arrayType.getComponentType()); + } + } Set<Class<?>> frameworkHandledTypes = new HashSet<Class<?>>(); @@ -292,15 +313,14 @@ public class SerializerMapGenerator extends Generator { frameworkHandledTypes.add(Map.class); frameworkHandledTypes.add(List.class); frameworkHandledTypes.add(Set.class); + frameworkHandledTypes.add(Byte.class); + frameworkHandledTypes.add(Character.class); } private boolean serializationHandledByFramework(JType setterType) { // Some types are handled by the framework at the moment. See #8449 // This method should be removed at some point. - if (setterType.isArray() != null) { - return true; - } if (setterType.isPrimitive() != null) { return true; } diff --git a/src/com/vaadin/terminal/gwt/widgetsetutils/WidgetMapGenerator.java b/src/com/vaadin/terminal/gwt/widgetsetutils/WidgetMapGenerator.java index 6d4289b173..b264a9c7fe 100644 --- a/src/com/vaadin/terminal/gwt/widgetsetutils/WidgetMapGenerator.java +++ b/src/com/vaadin/terminal/gwt/widgetsetutils/WidgetMapGenerator.java @@ -22,8 +22,8 @@ import com.google.gwt.core.ext.typeinfo.JClassType; import com.google.gwt.core.ext.typeinfo.TypeOracle; import com.google.gwt.user.rebind.ClassSourceFileComposerFactory; import com.google.gwt.user.rebind.SourceWriter; -import com.vaadin.terminal.gwt.client.ComponentConnector; import com.vaadin.terminal.gwt.client.Connector; +import com.vaadin.terminal.gwt.client.ServerConnector; import com.vaadin.terminal.gwt.client.ui.Connect; import com.vaadin.terminal.gwt.client.ui.Connect.LoadStyle; import com.vaadin.terminal.gwt.client.ui.UnknownComponentConnector; @@ -71,7 +71,7 @@ import com.vaadin.terminal.gwt.server.ClientConnector; */ public class WidgetMapGenerator extends Generator { - private static String componentConnectorClassName = ComponentConnector.class + private static String serverConnectorClassName = ServerConnector.class .getName(); private String packageName; @@ -115,7 +115,7 @@ public class WidgetMapGenerator extends Generator { return; } logger.log(Type.INFO, - "Detecting Vaadin components in classpath to generate WidgetMapImpl.java ..."); + "Detecting Vaadin connectors in classpath to generate WidgetMapImpl.java ..."); Date date = new Date(); // init composer, set class properties, create source writer @@ -128,7 +128,7 @@ public class WidgetMapGenerator extends Generator { SourceWriter sourceWriter = composer.createSourceWriter(context, printWriter); - Collection<Class<? extends ComponentConnector>> connectors = getUsedConnectors(context + Collection<Class<? extends ServerConnector>> connectors = getUsedConnectors(context .getTypeOracle()); validateConnectors(logger, connectors); @@ -149,12 +149,11 @@ public class WidgetMapGenerator extends Generator { } private void validateConnectors(TreeLogger logger, - Collection<Class<? extends ComponentConnector>> connectors) { + Collection<Class<? extends ServerConnector>> connectors) { - Iterator<Class<? extends ComponentConnector>> iter = connectors - .iterator(); + Iterator<Class<? extends ServerConnector>> iter = connectors.iterator(); while (iter.hasNext()) { - Class<? extends ComponentConnector> connectorClass = iter.next(); + Class<? extends ServerConnector> connectorClass = iter.next(); Connect annotation = connectorClass.getAnnotation(Connect.class); if (!ClientConnector.class.isAssignableFrom(annotation.value())) { logger.log( @@ -173,13 +172,13 @@ public class WidgetMapGenerator extends Generator { } private void logConnectors(TreeLogger logger, GeneratorContext context, - Collection<Class<? extends ComponentConnector>> connectors) { + Collection<Class<? extends ServerConnector>> connectors) { logger.log(Type.INFO, "Widget set will contain implementations for following component connectors: "); TreeSet<String> classNames = new TreeSet<String>(); HashMap<String, String> loadStyle = new HashMap<String, String>(); - for (Class<? extends ComponentConnector> connectorClass : connectors) { + for (Class<? extends ServerConnector> connectorClass : connectors) { String className = connectorClass.getCanonicalName(); classNames.add(className); if (getLoadStyle(connectorClass) == LoadStyle.DEFERRED) { @@ -208,16 +207,16 @@ public class WidgetMapGenerator extends Generator { * widgetset */ @SuppressWarnings("unchecked") - private Collection<Class<? extends ComponentConnector>> getUsedConnectors( + private Collection<Class<? extends ServerConnector>> getUsedConnectors( TypeOracle typeOracle) { JClassType connectorType = typeOracle.findType(Connector.class .getName()); - Collection<Class<? extends ComponentConnector>> connectors = new HashSet<Class<? extends ComponentConnector>>(); + Collection<Class<? extends ServerConnector>> connectors = new HashSet<Class<? extends ServerConnector>>(); for (JClassType jClassType : connectorType.getSubtypes()) { Connect annotation = jClassType.getAnnotation(Connect.class); if (annotation != null) { try { - Class<? extends ComponentConnector> clazz = (Class<? extends ComponentConnector>) Class + Class<? extends ServerConnector> clazz = (Class<? extends ServerConnector>) Class .forName(jClassType.getQualifiedSourceName()); connectors.add(clazz); } catch (ClassNotFoundException e) { @@ -242,15 +241,14 @@ public class WidgetMapGenerator extends Generator { * @return true iff the widget for given component should be lazy loaded by * the client side engine */ - protected LoadStyle getLoadStyle( - Class<? extends ComponentConnector> connector) { + protected LoadStyle getLoadStyle(Class<? extends ServerConnector> connector) { Connect annotation = connector.getAnnotation(Connect.class); return annotation.loadStyle(); } private void generateInstantiatorMethod( SourceWriter sourceWriter, - Collection<Class<? extends ComponentConnector>> connectorsHavingComponentAnnotation) { + Collection<Class<? extends ServerConnector>> connectorsHavingComponentAnnotation) { Collection<Class<?>> deferredWidgets = new LinkedList<Class<?>>(); @@ -258,16 +256,16 @@ public class WidgetMapGenerator extends Generator { // lookup with index than with the hashmap sourceWriter.println("public void ensureInstantiator(Class<? extends " - + componentConnectorClassName + "> classType) {"); + + serverConnectorClassName + "> classType) {"); sourceWriter.println("if(!instmap.containsKey(classType)){"); boolean first = true; - ArrayList<Class<? extends ComponentConnector>> lazyLoadedWidgets = new ArrayList<Class<? extends ComponentConnector>>(); + ArrayList<Class<? extends ServerConnector>> lazyLoadedConnectors = new ArrayList<Class<? extends ServerConnector>>(); - HashSet<Class<? extends com.vaadin.terminal.gwt.client.ComponentConnector>> connectorsWithInstantiator = new HashSet<Class<? extends com.vaadin.terminal.gwt.client.ComponentConnector>>(); + HashSet<Class<? extends ServerConnector>> connectorsWithInstantiator = new HashSet<Class<? extends ServerConnector>>(); - for (Class<? extends ComponentConnector> class1 : connectorsHavingComponentAnnotation) { - Class<? extends ComponentConnector> clientClass = class1; + for (Class<? extends ServerConnector> class1 : connectorsHavingComponentAnnotation) { + Class<? extends ServerConnector> clientClass = class1; if (connectorsWithInstantiator.contains(clientClass)) { continue; } @@ -284,7 +282,7 @@ public class WidgetMapGenerator extends Generator { + ".class) {"); String instantiator = "new WidgetInstantiator() {\n public " - + componentConnectorClassName + + serverConnectorClassName + " get() {\n return GWT.create(" + clientClass.getName() + ".class );\n}\n}\n"; @@ -298,7 +296,7 @@ public class WidgetMapGenerator extends Generator { + clientClass.getName() + ".class," + instantiator + ");}});\n"); - lazyLoadedWidgets.add(class1); + lazyLoadedConnectors.add(class1); if (loadStyle == LoadStyle.DEFERRED) { deferredWidgets.add(class1); @@ -321,8 +319,8 @@ public class WidgetMapGenerator extends Generator { sourceWriter.println("}"); sourceWriter.println("public Class<? extends " - + componentConnectorClassName - + ">[] getDeferredLoadedWidgets() {"); + + serverConnectorClassName + + ">[] getDeferredLoadedConnectors() {"); sourceWriter.println("return new Class[] {"); first = true; @@ -344,11 +342,11 @@ public class WidgetMapGenerator extends Generator { // TODO an index of last ensured widget in array - sourceWriter.println("public " + componentConnectorClassName - + " instantiate(Class<? extends " + componentConnectorClassName + sourceWriter.println("public " + serverConnectorClassName + + " instantiate(Class<? extends " + serverConnectorClassName + "> classType) {"); sourceWriter.indent(); - sourceWriter.println(componentConnectorClassName + sourceWriter.println(serverConnectorClassName + " p = super.instantiate(classType); if(p!= null) return p;"); sourceWriter.println("return instmap.get(classType).get();"); @@ -365,17 +363,17 @@ public class WidgetMapGenerator extends Generator { */ private void generateImplementationDetector( SourceWriter sourceWriter, - Collection<Class<? extends ComponentConnector>> paintablesHavingWidgetAnnotation) { + Collection<Class<? extends ServerConnector>> paintablesHavingWidgetAnnotation) { sourceWriter .println("public Class<? extends " - + componentConnectorClassName + + serverConnectorClassName + "> " + "getConnectorClassForServerSideClassName(String fullyQualifiedName) {"); sourceWriter.indent(); sourceWriter .println("fullyQualifiedName = fullyQualifiedName.intern();"); - for (Class<? extends ComponentConnector> connectorClass : paintablesHavingWidgetAnnotation) { + for (Class<? extends ServerConnector> connectorClass : paintablesHavingWidgetAnnotation) { Class<? extends ClientConnector> clientConnectorClass = getClientConnectorClass(connectorClass); sourceWriter.print("if ( fullyQualifiedName == \""); sourceWriter.print(clientConnectorClass.getName()); @@ -393,7 +391,7 @@ public class WidgetMapGenerator extends Generator { } private static Class<? extends ClientConnector> getClientConnectorClass( - Class<? extends ComponentConnector> connectorClass) { + Class<? extends ServerConnector> connectorClass) { Connect annotation = connectorClass.getAnnotation(Connect.class); return (Class<? extends ClientConnector>) annotation.value(); } diff --git a/src/com/vaadin/tools/WidgetsetCompiler.java b/src/com/vaadin/tools/WidgetsetCompiler.java index 323fb17e32..407f0e2387 100644 --- a/src/com/vaadin/tools/WidgetsetCompiler.java +++ b/src/com/vaadin/tools/WidgetsetCompiler.java @@ -30,12 +30,12 @@ import com.vaadin.terminal.gwt.widgetsetutils.WidgetSetBuilder; * The source directory containing widgetset and related classes must be * included in the classpath, as well as the gwt-dev-[platform].jar and other * relevant JARs. + * + * @deprecated with Java 6, can use com.google.gwt.dev.Compiler directly (also in Eclipse plug-in etc.) */ +@Deprecated public class WidgetsetCompiler { - private static final Logger logger = Logger - .getLogger(WidgetsetCompiler.class.getName()); - /** * @param args * same arguments as for com.google.gwt.dev.Compiler @@ -72,7 +72,7 @@ public class WidgetsetCompiler { String[].class); method.invoke(null, new Object[] { args }); } catch (Throwable thr) { - logger.log(Level.SEVERE, + getLogger().log(Level.SEVERE, "Widgetset compilation failed", thr); } } @@ -82,7 +82,11 @@ public class WidgetsetCompiler { runThread.join(); System.out.println("Widgetset compilation finished"); } catch (Throwable thr) { - logger.log(Level.SEVERE, "Widgetset compilation failed", thr); + getLogger().log(Level.SEVERE, "Widgetset compilation failed", thr); } } + + private static final Logger getLogger() { + return Logger.getLogger(WidgetsetCompiler.class.getName()); + } } diff --git a/src/com/vaadin/ui/AbsoluteLayout.java b/src/com/vaadin/ui/AbsoluteLayout.java index 9ba005f75a..7d8c402fc9 100644 --- a/src/com/vaadin/ui/AbsoluteLayout.java +++ b/src/com/vaadin/ui/AbsoluteLayout.java @@ -161,7 +161,8 @@ public class AbsoluteLayout extends AbstractLayout implements // connectorId unless the component is attached to the application so // the String->String map cannot be populated in internal* either. Map<String, String> connectorToPosition = new HashMap<String, String>(); - for (Component c : this) { + for (Iterator<Component> ci = getComponentIterator(); ci.hasNext();) { + Component c = ci.next(); connectorToPosition.put(c.getConnectorId(), getPosition(c) .getCSSString()); } diff --git a/src/com/vaadin/ui/AbstractComponent.java b/src/com/vaadin/ui/AbstractComponent.java index 554d7806f9..ba0e5db89c 100644 --- a/src/com/vaadin/ui/AbstractComponent.java +++ b/src/com/vaadin/ui/AbstractComponent.java @@ -5,20 +5,13 @@ package com.vaadin.ui; import java.io.Serializable; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; -import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.Iterator; -import java.util.LinkedList; import java.util.List; import java.util.Locale; -import java.util.Map; -import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -27,18 +20,14 @@ import com.vaadin.event.ActionManager; import com.vaadin.event.EventRouter; import com.vaadin.event.MethodEventSource; import com.vaadin.event.ShortcutListener; +import com.vaadin.terminal.AbstractClientConnector; import com.vaadin.terminal.ErrorMessage; import com.vaadin.terminal.Resource; import com.vaadin.terminal.Terminal; import com.vaadin.terminal.gwt.client.ComponentState; -import com.vaadin.terminal.gwt.client.communication.ClientRpc; -import com.vaadin.terminal.gwt.client.communication.ServerRpc; -import com.vaadin.terminal.gwt.server.ClientMethodInvocation; +import com.vaadin.terminal.gwt.server.ClientConnector; import com.vaadin.terminal.gwt.server.ComponentSizeValidator; import com.vaadin.terminal.gwt.server.ResourceReference; -import com.vaadin.terminal.gwt.server.RpcManager; -import com.vaadin.terminal.gwt.server.RpcTarget; -import com.vaadin.terminal.gwt.server.ServerRpcManager; import com.vaadin.tools.ReflectTools; /** @@ -53,7 +42,8 @@ import com.vaadin.tools.ReflectTools; * @since 3.0 */ @SuppressWarnings("serial") -public abstract class AbstractComponent implements Component, MethodEventSource { +public abstract class AbstractComponent extends AbstractClientConnector + implements Component, MethodEventSource { /* Private members */ @@ -64,11 +54,6 @@ public abstract class AbstractComponent implements Component, MethodEventSource private Object applicationData; /** - * The container this component resides in. - */ - private HasComponents parent = null; - - /** * The EventRouter used for the event model. */ private EventRouter eventRouter = null; @@ -88,11 +73,6 @@ public abstract class AbstractComponent implements Component, MethodEventSource */ private boolean delayedFocus; - /** - * List of repaint request listeners or null if not listened at all. - */ - private LinkedList<RepaintRequestListener> repaintRequestListeners = null; - /* Sizeable fields */ private float width = SIZE_UNDEFINED; @@ -110,31 +90,6 @@ public abstract class AbstractComponent implements Component, MethodEventSource */ private ActionManager actionManager; - /** - * A map from client to server RPC interface class to the RPC call manager - * that handles incoming RPC calls for that interface. - */ - private Map<Class<?>, RpcManager> rpcManagerMap = new HashMap<Class<?>, RpcManager>(); - - /** - * A map from server to client RPC interface class to the RPC proxy that - * sends ourgoing RPC calls for that interface. - */ - private Map<Class<?>, ClientRpc> rpcProxyMap = new HashMap<Class<?>, ClientRpc>(); - - /** - * Shared state object to be communicated from the server to the client when - * modified. - */ - private ComponentState sharedState; - - /** - * Pending RPC method invocations to be sent. - */ - private ArrayList<ClientMethodInvocation> pendingInvocations = new ArrayList<ClientMethodInvocation>(); - - private String connectorId; - /* Constructor */ /** @@ -287,6 +242,7 @@ public abstract class AbstractComponent implements Component, MethodEventSource if (locale != null) { return locale; } + HasComponents parent = getParent(); if (parent != null) { return parent.getLocale(); } @@ -378,21 +334,18 @@ public abstract class AbstractComponent implements Component, MethodEventSource * * @see com.vaadin.terminal.gwt.client.Connector#isConnectorEnabled() */ + @Override public boolean isConnectorEnabled() { - if (getParent() == null) { - // No parent -> the component cannot receive updates from the client + if (!isVisible()) { + return false; + } else if (!isEnabled()) { + return false; + } else if (!super.isConnectorEnabled()) { + return false; + } else if (!getParent().isComponentVisible(this)) { return false; } else { - boolean thisEnabledAndVisible = isEnabled() && isVisible(); - if (!thisEnabledAndVisible) { - return false; - } - - if (!getParent().isConnectorEnabled()) { - return false; - } - - return getParent().isComponentVisible(this); + return true; } } @@ -529,8 +482,20 @@ public abstract class AbstractComponent implements Component, MethodEventSource * Gets the component's parent component. Don't add a JavaDoc comment here, * we use the default documentation from implemented interface. */ + @Override public HasComponents getParent() { - return parent; + return (HasComponents) super.getParent(); + } + + @Override + public void setParent(ClientConnector parent) { + if (parent == null || parent instanceof HasComponents) { + super.setParent(parent); + } else { + throw new IllegalArgumentException( + "The parent of a Component must implement HasComponents, which " + + parent.getClass() + " doesn't do."); + } } /** @@ -558,36 +523,6 @@ public abstract class AbstractComponent implements Component, MethodEventSource return null; } - /* - * Sets the parent component. Don't add a JavaDoc comment here, we use the - * default documentation from implemented interface. - */ - public void setParent(HasComponents parent) { - - // If the parent is not changed, don't do anything - if (parent == this.parent) { - return; - } - - if (parent != null && this.parent != null) { - throw new IllegalStateException(getClass().getName() - + " already has a parent."); - } - - // Send detach event if the component have been connected to a window - if (getApplication() != null) { - detach(); - } - - // Connect to new parent - this.parent = parent; - - // Send attach event if connected to a window - if (getApplication() != null) { - attach(); - } - } - /** * Gets the error message for this component. * @@ -648,12 +583,10 @@ public abstract class AbstractComponent implements Component, MethodEventSource * Gets the parent window of the component. Don't add a JavaDoc comment * here, we use the default documentation from implemented interface. */ + @Override public Root getRoot() { - if (parent == null) { - return null; - } else { - return parent.getRoot(); - } + // Just make method from implemented Component interface public + return super.getRoot(); } /* @@ -661,9 +594,9 @@ public abstract class AbstractComponent implements Component, MethodEventSource * comment here, we use the default documentation from implemented * interface. */ + @Override public void attach() { - getRoot().componentAttached(this); - requestRepaint(); + super.attach(); if (delayedFocus) { focus(); } @@ -674,13 +607,14 @@ public abstract class AbstractComponent implements Component, MethodEventSource * Detach the component from application. Don't add a JavaDoc comment here, * we use the default documentation from implemented interface. */ + @Override public void detach() { + super.detach(); if (actionManager != null) { // Remove any existing viewer. Root cast is just to make the // compiler happy actionManager.setViewer((Root) null); } - getRoot().componentDetached(this); } /** @@ -717,12 +651,10 @@ public abstract class AbstractComponent implements Component, MethodEventSource * @return the parent application of the component or <code>null</code>. * @see #attach() */ + @Override public Application getApplication() { - if (parent == null) { - return null; - } else { - return parent.getApplication(); - } + // Just make method inherited from Component interface public + return super.getApplication(); } /** @@ -762,11 +694,9 @@ public abstract class AbstractComponent implements Component, MethodEventSource * * @return updated component shared state */ + @Override public ComponentState getState() { - if (null == sharedState) { - sharedState = createState(); - } - return sharedState; + return (ComponentState) super.getState(); } /* @@ -801,95 +731,15 @@ public abstract class AbstractComponent implements Component, MethodEventSource } } - /** - * Creates the shared state bean to be used in server to client - * communication. - * <p> - * By default a state object of the defined return type of - * {@link #getState()} is created. Subclasses can override this method and - * return a new instance of the correct state class but this should rarely - * be necessary. - * </p> - * <p> - * No configuration of the values of the state should be performed in - * {@link #createState()}. - * - * @since 7.0 - * - * @return new shared state object - */ - protected ComponentState createState() { - try { - return getStateType().newInstance(); - } catch (Exception e) { - throw new RuntimeException( - "Error creating state of type " + getStateType().getName() - + " for " + getClass().getName(), e); - } - } - - /* (non-Javadoc) - * @see com.vaadin.terminal.gwt.server.ClientConnector#getStateType() - */ - public Class<? extends ComponentState> getStateType() { - try { - Method m = getClass().getMethod("getState", (Class[]) null); - Class<? extends ComponentState> type = (Class<? extends ComponentState>) m - .getReturnType(); - return type; - } catch (Exception e) { - throw new RuntimeException("Error finding state type for " - + getClass().getName(), e); - } - } - /* Documentation copied from interface */ + @Override public void requestRepaint() { // Invisible components (by flag in this particular component) do not // need repaints if (!getState().isVisible()) { return; } - - fireRequestRepaintEvent(); - } - - /** - * Fires the repaint request event. - * - * @param alreadyNotified - */ - // Notify listeners only once - private void fireRequestRepaintEvent() { - // Notify the listeners - if (repaintRequestListeners != null - && !repaintRequestListeners.isEmpty()) { - final Object[] listeners = repaintRequestListeners.toArray(); - final RepaintRequestEvent event = new RepaintRequestEvent(this); - for (int i = 0; i < listeners.length; i++) { - ((RepaintRequestListener) listeners[i]).repaintRequested(event); - } - } - } - - /* Documentation copied from interface */ - public void addListener(RepaintRequestListener listener) { - if (repaintRequestListeners == null) { - repaintRequestListeners = new LinkedList<RepaintRequestListener>(); - } - if (!repaintRequestListeners.contains(listener)) { - repaintRequestListeners.add(listener); - } - } - - /* Documentation copied from interface */ - public void removeListener(RepaintRequestListener listener) { - if (repaintRequestListeners != null) { - repaintRequestListeners.remove(listener); - if (repaintRequestListeners.isEmpty()) { - repaintRequestListeners = null; - } - } + super.requestRepaint(); } /* General event framework */ @@ -1155,15 +1005,6 @@ public abstract class AbstractComponent implements Component, MethodEventSource * are found. */ public Collection<?> getListeners(Class<?> eventType) { - if (eventType.isAssignableFrom(RepaintRequestEvent.class)) { - // RepaintRequestListeners are not stored in eventRouter - if (repaintRequestListeners == null) { - return Collections.EMPTY_LIST; - } else { - return Collections - .unmodifiableCollection(repaintRequestListeners); - } - } if (eventRouter == null) { return Collections.EMPTY_LIST; } @@ -1512,159 +1353,4 @@ public abstract class AbstractComponent implements Component, MethodEventSource actionManager.removeAction(shortcut); } } - - /** - * Registers an RPC interface implementation for this component. - * - * A component can listen to multiple RPC interfaces, and subclasses can - * register additional implementations. - * - * @since 7.0 - * - * @param implementation - * RPC interface implementation - * @param rpcInterfaceType - * RPC interface class for which the implementation should be - * registered - */ - protected <T> void registerRpc(T implementation, Class<T> rpcInterfaceType) { - rpcManagerMap.put(rpcInterfaceType, new ServerRpcManager<T>( - implementation, rpcInterfaceType)); - } - - /** - * Registers an RPC interface implementation for this component. - * - * A component can listen to multiple RPC interfaces, and subclasses can - * register additional implementations. - * - * @since 7.0 - * - * @param implementation - * RPC interface implementation. Also used to deduce the type. - */ - protected <T extends ServerRpc> void registerRpc(T implementation) { - Class<?> cls = implementation.getClass(); - Class<?>[] interfaces = cls.getInterfaces(); - while (interfaces.length == 0) { - // Search upwards until an interface is found. It must be found as T - // extends ServerRpc - cls = cls.getSuperclass(); - interfaces = cls.getInterfaces(); - } - if (interfaces.length != 1 - || !(ServerRpc.class.isAssignableFrom(interfaces[0]))) { - throw new RuntimeException( - "Use registerRpc(T implementation, Class<T> rpcInterfaceType) if the Rpc implementation implements more than one interface"); - } - Class<T> type = (Class<T>) interfaces[0]; - registerRpc(implementation, type); - } - - /** - * Returns an RPC proxy for a given server to client RPC interface for this - * component. - * - * TODO more javadoc, subclasses, ... - * - * @param rpcInterface - * RPC interface type - * - * @since 7.0 - */ - public <T extends ClientRpc> T getRpcProxy(final Class<T> rpcInterface) { - // create, initialize and return a dynamic proxy for RPC - try { - if (!rpcProxyMap.containsKey(rpcInterface)) { - Class<T> proxyClass = (Class) Proxy.getProxyClass( - rpcInterface.getClassLoader(), rpcInterface); - Constructor<T> constructor = proxyClass - .getConstructor(InvocationHandler.class); - T rpcProxy = constructor.newInstance(new RpcInvoicationHandler( - rpcInterface)); - // cache the proxy - rpcProxyMap.put(rpcInterface, rpcProxy); - } - return (T) rpcProxyMap.get(rpcInterface); - } catch (Exception e) { - // TODO exception handling? - throw new RuntimeException(e); - } - } - - private class RpcInvoicationHandler implements InvocationHandler, - Serializable { - - private String rpcInterfaceName; - - public RpcInvoicationHandler(Class<?> rpcInterface) { - rpcInterfaceName = rpcInterface.getName().replaceAll("\\$", "."); - } - - public Object invoke(Object proxy, Method method, Object[] args) - throws Throwable { - addMethodInvocationToQueue(rpcInterfaceName, method, args); - // TODO no need to do full repaint if only RPC calls - requestRepaint(); - return null; - } - - } - - /** - * For internal use: adds a method invocation to the pending RPC call queue. - * - * @param interfaceName - * RPC interface name - * @param methodName - * RPC method name - * @param parameters - * RPC vall parameters - * - * @since 7.0 - */ - protected void addMethodInvocationToQueue(String interfaceName, - Method method, Object[] parameters) { - // add to queue - pendingInvocations.add(new ClientMethodInvocation(this, interfaceName, - method, parameters)); - } - - /** - * @see RpcTarget#getRpcManager(Class) - * - * @param rpcInterface - * RPC interface for which a call was made - * @return RPC Manager handling calls for the interface - * - * @since 7.0 - */ - public RpcManager getRpcManager(Class<?> rpcInterface) { - return rpcManagerMap.get(rpcInterface); - } - - public List<ClientMethodInvocation> retrievePendingRpcCalls() { - if (pendingInvocations.isEmpty()) { - return Collections.emptyList(); - } else { - List<ClientMethodInvocation> result = pendingInvocations; - pendingInvocations = new ArrayList<ClientMethodInvocation>(); - return Collections.unmodifiableList(result); - } - } - - public String getConnectorId() { - if (connectorId == null) { - if (getApplication() == null) { - throw new RuntimeException( - "Component must be attached to an application when getConnectorId() is called for the first time"); - } - connectorId = getApplication().createConnectorId(this); - } - return connectorId; - } - - private Logger getLogger() { - return Logger.getLogger(AbstractComponent.class.getName()); - } } diff --git a/src/com/vaadin/ui/AbstractComponentContainer.java b/src/com/vaadin/ui/AbstractComponentContainer.java index 1c857a03cd..8ef458b704 100644 --- a/src/com/vaadin/ui/AbstractComponentContainer.java +++ b/src/com/vaadin/ui/AbstractComponentContainer.java @@ -71,36 +71,6 @@ public abstract class AbstractComponentContainer extends AbstractComponent } } - /** - * Notifies all contained components that the container is attached to a - * window. - * - * @see com.vaadin.ui.Component#attach() - */ - @Override - public void attach() { - super.attach(); - - for (final Iterator<Component> i = getComponentIterator(); i.hasNext();) { - (i.next()).attach(); - } - } - - /** - * Notifies all contained components that the container is detached from a - * window. - * - * @see com.vaadin.ui.Component#detach() - */ - @Override - public void detach() { - super.detach(); - - for (final Iterator<Component> i = getComponentIterator(); i.hasNext();) { - (i.next()).detach(); - } - } - /* Events */ private static final Method COMPONENT_ATTACHED_METHOD; @@ -355,46 +325,6 @@ public abstract class AbstractComponentContainer extends AbstractComponent true); } - public void requestRepaintAll() { - requestRepaintAll(this); - } - - /** - * Helper that implements the logic needed by requestRepaintAll. Calls - * requestRepaintAll/requestRepaint for the component container and all its - * children recursively. - * - * @param container - */ - public static void requestRepaintAll(HasComponents container) { - container.requestRepaint(); - if (container instanceof Panel) { - Panel p = (Panel) container; - // #2924 Panel is invalid, really invalid. - // Panel.getComponentIterator returns the children of content, not - // of Panel... - if (p.getContent() != null) { - p.getContent().requestRepaint(); - } - } - for (Iterator<Component> childIterator = container - .getComponentIterator(); childIterator.hasNext();) { - Component c = childIterator.next(); - if (c instanceof HasComponents) { - requestRepaintAll((HasComponents) c); - } else { - c.requestRepaint(); - } - } - } - - /** - * Returns an iterator for the child components. - * - * @return An iterator for the child components. - * @see #getComponentIterator() - * @since 7.0.0 - */ public Iterator<Component> iterator() { return getComponentIterator(); } diff --git a/src/com/vaadin/ui/AbstractField.java b/src/com/vaadin/ui/AbstractField.java index 4efed11e2c..ce62fb43ed 100644 --- a/src/com/vaadin/ui/AbstractField.java +++ b/src/com/vaadin/ui/AbstractField.java @@ -14,14 +14,14 @@ import java.util.LinkedList; import java.util.List; import java.util.logging.Logger; -import com.vaadin.Application; import com.vaadin.data.Buffered; import com.vaadin.data.Property; import com.vaadin.data.Validatable; import com.vaadin.data.Validator; import com.vaadin.data.Validator.InvalidValueException; import com.vaadin.data.util.converter.Converter; -import com.vaadin.data.util.converter.ConverterFactory; +import com.vaadin.data.util.converter.Converter.ConversionException; +import com.vaadin.data.util.converter.ConverterUtil; import com.vaadin.event.Action; import com.vaadin.event.ShortcutAction; import com.vaadin.event.ShortcutListener; @@ -591,7 +591,7 @@ public abstract class AbstractField<T> extends AbstractComponent implements // Commits the value to datasource committingValueToDataSource = true; getPropertyDataSource().setValue( - convertToDataSource(newFieldValue)); + convertToModel(newFieldValue)); // The buffer is now unmodified setModified(false); @@ -701,8 +701,8 @@ public abstract class AbstractField<T> extends AbstractComponent implements // Check if the current converter is compatible. if (newDataSource != null - && (getConverter() == null || !newDataSource.getType() - .isAssignableFrom(getConverter().getModelType()))) { + && !ConverterUtil.canConverterHandle(getConverter(), getType(), + newDataSource.getType())) { // Changing from e.g. Number -> Double should set a new converter, // changing from Double -> Number can keep the old one (Property // accepts Number) @@ -760,15 +760,9 @@ public abstract class AbstractField<T> extends AbstractComponent implements * from */ public void setConverter(Class<?> datamodelType) { - Converter<T, ?> converter = null; - - Application app = Application.getCurrentApplication(); - if (app != null) { - ConverterFactory factory = app.getConverterFactory(); - converter = (Converter<T, ?>) factory.createConverter(getType(), - datamodelType); - } - setConverter(converter); + Converter<T, ?> c = (Converter<T, ?>) ConverterUtil.getConverter( + getType(), datamodelType, getApplication()); + setConverter(c); } /** @@ -782,26 +776,9 @@ public abstract class AbstractField<T> extends AbstractComponent implements * if there is no converter and the type is not compatible with * the data source type. */ - @SuppressWarnings("unchecked") - private T convertFromDataSource(Object newValue) - throws Converter.ConversionException { - if (converter != null) { - return converter.convertToPresentation(newValue, getLocale()); - } - if (newValue == null) { - return null; - } - - if (getType().isAssignableFrom(newValue.getClass())) { - return (T) newValue; - } else { - throw new Converter.ConversionException( - "Unable to convert value of type " - + newValue.getClass().getName() - + " to " - + getType() - + ". No converter is set and the types are not compatible."); - } + private T convertFromDataSource(Object newValue) { + return ConverterUtil.convertFromModel(newValue, getType(), + getConverter(), getLocale()); } /** @@ -815,40 +792,21 @@ public abstract class AbstractField<T> extends AbstractComponent implements * if there is no converter and the type is not compatible with * the data source type. */ - private Object convertToDataSource(T fieldValue) + private Object convertToModel(T fieldValue) throws Converter.ConversionException { - if (converter != null) { - /* - * If there is a converter, always use it. It must convert or throw - * an exception. - */ - try { - return converter.convertToModel(fieldValue, getLocale()); - } catch (com.vaadin.data.util.converter.Converter.ConversionException e) { - throw new Converter.ConversionException( - getConversionError(converter.getModelType()), e); + try { + Class<?> modelType = null; + Property pd = getPropertyDataSource(); + if (pd != null) { + modelType = pd.getType(); + } else if (getConverter() != null) { + modelType = getConverter().getModelType(); } - } - - if (fieldValue == null) { - // Null should always be passed through the converter but if there - // is no converter we can safely return null - return null; - } - - // check that the value class is compatible with the data source type - // (if data source set) or field type - Class<?> type; - if (getPropertyDataSource() != null) { - type = getPropertyDataSource().getType(); - } else { - type = getType(); - } - - if (type.isAssignableFrom(fieldValue.getClass())) { - return fieldValue; - } else { - throw new Converter.ConversionException(getConversionError(type)); + return ConverterUtil.convertToModel(fieldValue, + (Class<Object>) modelType, getConverter(), getLocale()); + } catch (ConversionException e) { + throw new ConversionException( + getConversionError(converter.getModelType()), e); } } @@ -880,7 +838,7 @@ public abstract class AbstractField<T> extends AbstractComponent implements * @return The converted value that is compatible with the data source type */ public Object getConvertedValue() { - return convertToDataSource(getFieldValue()); + return convertToModel(getFieldValue()); } /** diff --git a/src/com/vaadin/ui/AbstractJavaScriptComponent.java b/src/com/vaadin/ui/AbstractJavaScriptComponent.java new file mode 100644 index 0000000000..95c45f55f9 --- /dev/null +++ b/src/com/vaadin/ui/AbstractJavaScriptComponent.java @@ -0,0 +1,161 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.ui; + +import com.vaadin.terminal.JavaScriptCallbackHelper; +import com.vaadin.terminal.gwt.client.ui.JavaScriptComponentState; +import com.vaadin.terminal.gwt.client.ui.JavaScriptWidget; + +/** + * Base class for Components with all client-side logic implemented using + * JavaScript. + * <p> + * When a new JavaScript component is initialized in the browser, the framework + * will look for a globally defined JavaScript function that will initialize the + * component. The name of the initialization function is formed by replacing . + * with _ in the name of the server-side class. If no such function is defined, + * each super class is used in turn until a match is found. The framework will + * thus first attempt with <code>com_example_MyComponent</code> for the + * server-side + * <code>com.example.MyComponent extends AbstractJavaScriptComponent</code> + * class. If MyComponent instead extends <code>com.example.SuperComponent</code> + * , then <code>com_example_SuperComponent</code> will also be attempted if + * <code>com_example_MyComponent</code> has not been defined. + * <p> + * JavaScript components have a very simple GWT widget ({@link JavaScriptWidget} + * ) just consisting of a <code>div</code> element to which the JavaScript code + * should initialize its own user interface. + * <p> + * The initialization function will be called with <code>this</code> pointing to + * a connector wrapper object providing integration to Vaadin with the following + * functions: + * <ul> + * <li><code>getConnectorId()</code> - returns a string with the id of the + * connector.</li> + * <li><code>getParentId([connectorId])</code> - returns a string with the id of + * the connector's parent. If <code>connectorId</code> is provided, the id of + * the parent of the corresponding connector with the passed id is returned + * instead.</li> + * <li><code>getWidgetElement([connectorId])</code> - returns the DOM Element + * that is the root of a connector's widget. <code>null</code> is returned if + * the connector can not be found or if the connector doesn't have a widget. If + * <code>connectorId</code> is not provided, the connector id of the current + * connector will be used.</li> + * <li><code>getState()</code> - returns an object corresponding to the shared + * state defined on the server. The scheme for conversion between Java and + * JavaScript types is described bellow.</li> + * <li><code>registerRpc([name, ] rpcObject)</code> - registers the + * <code>rpcObject</code> as a RPC handler. <code>rpcObject</code> should be an + * object with field containing functions for all eligible RPC functions. If + * <code>name</code> is provided, the RPC handler will only used for RPC calls + * for the RPC interface with the same fully qualified Java name. If no + * <code>name</code> is provided, the RPC handler will be used for all incoming + * RPC invocations where the RPC method name is defined as a function field in + * the handler. The scheme for conversion between Java types in the RPC + * interface definition and the JavaScript values passed as arguments to the + * handler functions is described bellow.</li> + * <li><code>getRpcProxy([name])</code> - returns an RPC proxy object. If + * <code>name</code> is provided, the proxy object will contain functions for + * all methods in the RPC interface with the same fully qualified name, provided + * a RPC handler has been registered by the server-side code. If no + * <code>name</code> is provided, the returned RPC proxy object will contain + * functions for all methods in all RPC interfaces registered for the connector + * on the server. If the same method name is present in multiple registered RPC + * interfaces, the corresponding function in the RPC proxy object will throw an + * exception when called. The scheme for conversion between Java types in the + * RPC interface and the JavaScript values that should be passed to the + * functions is described bellow.</li> + * </ul> + * The connector wrapper also supports these special functions: + * <ul> + * <li><code>onStateChange</code> - If the JavaScript code assigns a function to + * the field, that function is called whenever the contents of the shared state + * is changed.</li> + * <li>Any field name corresponding to a call to + * {@link #registerCallback(String, JavaScriptCallback)} on the server will + * automatically be present as a function that triggers the registered callback + * on the server.</li> + * <li>Any field name referred to using + * {@link #invokeCallback(String, Object...)} on the server will be called if a + * function has been assigned to the field.</li> + * </ul> + * <p> + * + * Values in the Shared State and in RPC calls are converted between Java and + * JavaScript using the following conventions: + * <ul> + * <li>Primitive Java numbers (byte, char, int, long, float, double) and their + * boxed types (Byte, Character, Integer, Long, Float, Double) are represented + * by JavaScript numbers.</li> + * <li>The primitive Java boolean and the boxed Boolean are represented by + * JavaScript booleans.</li> + * <li>Java Strings are represented by JavaScript strings.</li> + * <li>List, Set and all arrays in Java are represented by JavaScript arrays.</li> + * <li>Map<String, ?> in Java is represented by JavaScript object with fields + * corresponding to the map keys.</li> + * <li>Any other Java Map is represented by a JavaScript array containing two + * arrays, the first contains the keys and the second contains the values in the + * same order.</li> + * <li>A Java Bean is represented by a JavaScript object with fields + * corresponding to the bean's properties.</li> + * <li>A Java Connector is represented by a JavaScript string containing the + * connector's id.</li> + * <li>A pluggable serialization mechanism is provided for types not described + * here. Please refer to the documentation for specific types for serialization + * information.</li> + * </ul> + * + * @author Vaadin Ltd + * @version @VERSION@ + * @since 7.0.0 + */ +public class AbstractJavaScriptComponent extends AbstractComponent { + private JavaScriptCallbackHelper callbackHelper = new JavaScriptCallbackHelper( + this); + + @Override + protected <T> void registerRpc(T implementation, Class<T> rpcInterfaceType) { + super.registerRpc(implementation, rpcInterfaceType); + callbackHelper.registerRpc(rpcInterfaceType); + } + + /** + * Register a {@link JavaScriptCallback} that can be called from the + * JavaScript using the provided name. A JavaScript function with the + * provided name will be added to the connector wrapper object (initially + * available as <code>this</code>). Calling that JavaScript function will + * cause the call method in the registered {@link JavaScriptCallback} to be + * invoked with the same arguments. + * + * @param functionName + * the name that should be used for client-side callback + * @param javaScriptCallback + * the callback object that will be invoked when the JavaScript + * function is called + */ + protected void registerCallback(String functionName, + JavaScriptCallback javaScriptCallback) { + callbackHelper.registerCallback(functionName, javaScriptCallback); + } + + /** + * Invoke a named function that the connector JavaScript has added to the + * JavaScript connector wrapper object. The arguments should only contain + * data types that can be represented in JavaScript, including primitive + * boxing types, arrays, String, List, Set, Map, Connector and JavaBeans. + * + * @param name + * the name of the function + * @param arguments + * function arguments + */ + protected void invokeCallback(String name, Object... arguments) { + callbackHelper.invokeCallback(name, arguments); + } + + @Override + public JavaScriptComponentState getState() { + return (JavaScriptComponentState) super.getState(); + } +} diff --git a/src/com/vaadin/ui/AbstractSelect.java b/src/com/vaadin/ui/AbstractSelect.java index e586810b2d..6a927251af 100644 --- a/src/com/vaadin/ui/AbstractSelect.java +++ b/src/com/vaadin/ui/AbstractSelect.java @@ -24,7 +24,6 @@ import com.vaadin.event.Transferable; import com.vaadin.event.dd.DragAndDropEvent; import com.vaadin.event.dd.DropTarget; import com.vaadin.event.dd.TargetDetailsImpl; -import com.vaadin.event.dd.acceptcriteria.ClientCriterion; import com.vaadin.event.dd.acceptcriteria.ClientSideCriterion; import com.vaadin.event.dd.acceptcriteria.ContainsDataFlavor; import com.vaadin.event.dd.acceptcriteria.TargetDetailIs; @@ -33,8 +32,6 @@ import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; import com.vaadin.terminal.Resource; import com.vaadin.terminal.Vaadin6Component; -import com.vaadin.terminal.gwt.client.ui.dd.VIsOverId; -import com.vaadin.terminal.gwt.client.ui.dd.VItemIdIs; import com.vaadin.terminal.gwt.client.ui.dd.VerticalDropLocation; import com.vaadin.ui.AbstractSelect.ItemCaptionMode; @@ -1800,7 +1797,6 @@ public abstract class AbstractSelect extends AbstractField<Object> implements * * @since 6.3 */ - @ClientCriterion(VIsOverId.class) public static class TargetItemIs extends AbstractItemSetCriterion { /** @@ -1867,7 +1863,6 @@ public abstract class AbstractSelect extends AbstractField<Object> implements * * @since 6.3 */ - @ClientCriterion(VItemIdIs.class) public static class AcceptItem extends AbstractItemSetCriterion { /** diff --git a/src/com/vaadin/ui/AbstractSplitPanel.java b/src/com/vaadin/ui/AbstractSplitPanel.java index 5205952621..876d39f2ae 100644 --- a/src/com/vaadin/ui/AbstractSplitPanel.java +++ b/src/com/vaadin/ui/AbstractSplitPanel.java @@ -22,7 +22,7 @@ import com.vaadin.tools.ReflectTools; * AbstractSplitPanel. * * <code>AbstractSplitPanel</code> is base class for a component container that - * can contain two components. The comopnents are split by a divider element. + * can contain two components. The components are split by a divider element. * * @author Vaadin Ltd. * @version @@ -31,7 +31,10 @@ import com.vaadin.tools.ReflectTools; */ public abstract class AbstractSplitPanel extends AbstractComponentContainer { + // TODO use Unit in AbstractSplitPanelState and remove these private Unit posUnit; + private Unit posMinUnit; + private Unit posMaxUnit; private AbstractSplitPanelRpc rpc = new AbstractSplitPanelRpc() { @@ -41,13 +44,14 @@ public abstract class AbstractSplitPanel extends AbstractComponentContainer { } public void setSplitterPosition(float position) { - getState().getSplitterState().setPosition(position); + getSplitterState().setPosition(position); } }; public AbstractSplitPanel() { registerRpc(rpc); setSplitPosition(50, Unit.PERCENTAGE, false); + setSplitPositionLimits(0, Unit.PERCENTAGE, 100, Unit.PERCENTAGE); } /** @@ -101,6 +105,7 @@ public abstract class AbstractSplitPanel extends AbstractComponentContainer { * @param c * the component to be added. */ + @Override public void addComponent(Component c) { if (getFirstComponent() == null) { @@ -188,6 +193,7 @@ public abstract class AbstractSplitPanel extends AbstractComponentContainer { * @param c * the component to be removed. */ + @Override public void removeComponent(Component c) { super.removeComponent(c); @@ -204,6 +210,7 @@ public abstract class AbstractSplitPanel extends AbstractComponentContainer { * * @see com.vaadin.ui.ComponentContainer#getComponentIterator() */ + public Iterator<Component> getComponentIterator() { return new ComponentIterator(); } @@ -214,6 +221,7 @@ public abstract class AbstractSplitPanel extends AbstractComponentContainer { * * @return the number of contained components (zero, one or two) */ + public int getComponentCount() { int count = 0; if (getFirstComponent() != null) { @@ -226,6 +234,7 @@ public abstract class AbstractSplitPanel extends AbstractComponentContainer { } /* Documented in superclass */ + public void replaceComponent(Component oldComponent, Component newComponent) { if (oldComponent == getFirstComponent()) { setFirstComponent(newComponent); @@ -297,7 +306,7 @@ public abstract class AbstractSplitPanel extends AbstractComponentContainer { if (unit != Unit.PERCENTAGE) { pos = Math.round(pos); } - SplitterState splitterState = getState().getSplitterState(); + SplitterState splitterState = getSplitterState(); splitterState.setPosition(pos); splitterState.setPositionUnit(unit.getSymbol()); splitterState.setPositionReversed(reverse); @@ -313,7 +322,7 @@ public abstract class AbstractSplitPanel extends AbstractComponentContainer { * @return position of the splitter */ public float getSplitPosition() { - return getState().getSplitterState().getPosition(); + return getSplitterState().getPosition(); } /** @@ -326,6 +335,110 @@ public abstract class AbstractSplitPanel extends AbstractComponentContainer { } /** + * Sets the minimum split position to the given position and unit. If the + * split position is reversed, maximum and minimum are also reversed. + * + * @param pos + * the minimum position of the split + * @param unit + * the unit (from {@link Sizeable}) in which the size is given. + * Allowed units are UNITS_PERCENTAGE and UNITS_PIXELS + */ + public void setMinSplitPosition(int pos, Unit unit) { + setSplitPositionLimits(pos, unit, getSplitterState().getMaxPosition(), + posMaxUnit); + } + + /** + * Returns the current minimum position of the splitter, in + * {@link #getMinSplitPositionUnit()} units. + * + * @return the minimum position of the splitter + */ + public float getMinSplitPosition() { + return getSplitterState().getMinPosition(); + } + + /** + * Returns the unit of the minimum position of the splitter. + * + * @return the unit of the minimum position of the splitter + */ + public Unit getMinSplitPositionUnit() { + return posMinUnit; + } + + /** + * Sets the maximum split position to the given position and unit. If the + * split position is reversed, maximum and minimum are also reversed. + * + * @param pos + * the maximum position of the split + * @param unit + * the unit (from {@link Sizeable}) in which the size is given. + * Allowed units are UNITS_PERCENTAGE and UNITS_PIXELS + */ + public void setMaxSplitPosition(float pos, Unit unit) { + setSplitPositionLimits(getSplitterState().getMinPosition(), posMinUnit, + pos, unit); + } + + /** + * Returns the current maximum position of the splitter, in + * {@link #getMaxSplitPositionUnit()} units. + * + * @return the maximum position of the splitter + */ + public float getMaxSplitPosition() { + return getSplitterState().getMaxPosition(); + } + + /** + * Returns the unit of the maximum position of the splitter + * + * @return the unit of the maximum position of the splitter + */ + public Unit getMaxSplitPositionUnit() { + return posMaxUnit; + } + + /** + * Sets the maximum and minimum position of the splitter. If the split + * position is reversed, maximum and minimum are also reversed. + * + * @param minPos + * the new minimum position + * @param minPosUnit + * the unit (from {@link Sizeable}) in which the minimum position + * is given. + * @param maxPos + * the new maximum position + * @param maxPosUnit + * the unit (from {@link Sizeable}) in which the maximum position + * is given. + */ + private void setSplitPositionLimits(float minPos, Unit minPosUnit, + float maxPos, Unit maxPosUnit) { + if ((minPosUnit != Unit.PERCENTAGE && minPosUnit != Unit.PIXELS) + || (maxPosUnit != Unit.PERCENTAGE && maxPosUnit != Unit.PIXELS)) { + throw new IllegalArgumentException( + "Only percentage and pixel units are allowed"); + } + + SplitterState state = getSplitterState(); + + state.setMinPosition(minPos); + state.setMinPositionUnit(minPosUnit.getSymbol()); + posMinUnit = minPosUnit; + + state.setMaxPosition(maxPos); + state.setMaxPositionUnit(maxPosUnit.getSymbol()); + posMaxUnit = maxPosUnit; + + requestRepaint(); + } + + /** * Lock the SplitPanels position, disabling the user from dragging the split * handle. * @@ -333,7 +446,7 @@ public abstract class AbstractSplitPanel extends AbstractComponentContainer { * Set <code>true</code> if locked, <code>false</code> otherwise. */ public void setLocked(boolean locked) { - getState().getSplitterState().setLocked(locked); + getSplitterState().setLocked(locked); requestRepaint(); } @@ -344,7 +457,7 @@ public abstract class AbstractSplitPanel extends AbstractComponentContainer { * @return <code>true</code> if locked, <code>false</code> otherwise. */ public boolean isLocked() { - return getState().getSplitterState().isLocked(); + return getSplitterState().isLocked(); } /** @@ -394,4 +507,7 @@ public abstract class AbstractSplitPanel extends AbstractComponentContainer { return (AbstractSplitPanelState) super.getState(); } + private SplitterState getSplitterState() { + return getState().getSplitterState(); + } } diff --git a/src/com/vaadin/ui/Button.java b/src/com/vaadin/ui/Button.java index 876fe593e2..0abc50f26f 100644 --- a/src/com/vaadin/ui/Button.java +++ b/src/com/vaadin/ui/Button.java @@ -38,6 +38,7 @@ public class Button extends AbstractComponent implements Action.ShortcutNotifier { private ButtonServerRpc rpc = new ButtonServerRpc() { + public void click(MouseEventDetails mouseEventDetails) { fireClick(mouseEventDetails); } @@ -50,6 +51,7 @@ public class Button extends AbstractComponent implements }; FocusAndBlurServerRpcImpl focusBlurRpc = new FocusAndBlurServerRpcImpl(this) { + @Override protected void fireEvent(Event event) { Button.this.fireEvent(event); @@ -355,8 +357,6 @@ public class Button extends AbstractComponent implements protected ClickShortcut clickShortcut; - private int tabIndex = 0; - /** * Makes it possible to invoke a click on this button by pressing the given * {@link KeyCode} and (optional) {@link ModifierKey}s.<br/> @@ -469,13 +469,23 @@ public class Button extends AbstractComponent implements requestRepaint(); } + /* + * (non-Javadoc) + * + * @see com.vaadin.ui.Component.Focusable#getTabIndex() + */ public int getTabIndex() { - return tabIndex; + return getState().getTabIndex(); } + /* + * (non-Javadoc) + * + * @see com.vaadin.ui.Component.Focusable#setTabIndex(int) + */ public void setTabIndex(int tabIndex) { - this.tabIndex = tabIndex; - + getState().setTabIndex(tabIndex); + requestRepaint(); } @Override diff --git a/src/com/vaadin/ui/Component.java b/src/com/vaadin/ui/Component.java index 3632c4ca5e..81e0319880 100644 --- a/src/com/vaadin/ui/Component.java +++ b/src/com/vaadin/ui/Component.java @@ -304,41 +304,10 @@ public interface Component extends ClientConnector, Sizeable, Serializable { * </p> * * @return the parent component - * @see #setParent(Component) */ public HasComponents getParent(); /** - * Sets the parent component of the component. - * - * <p> - * This method automatically calls {@link #attach()} if the parent becomes - * attached to the application, regardless of whether it was attached - * previously. Conversely, if the parent is {@code null} and the component - * is attached to the application, {@link #detach()} is called for the - * component. - * </p> - * <p> - * This method is rarely called directly. The - * {@link ComponentContainer#addComponent(Component)} method is normally - * used for adding components to a container and it will call this method - * implicitly. - * </p> - * - * <p> - * It is not possible to change the parent without first setting the parent - * to {@code null}. - * </p> - * - * @param parent - * the parent component - * @throws IllegalStateException - * if a parent is given even though the component already has a - * parent - */ - public void setParent(HasComponents parent); - - /** * Tests whether the component is in the read-only mode. The user can not * change the value of a read-only component. As only {@link Field} * components normally have a value that can be input or changed by the @@ -528,21 +497,15 @@ public interface Component extends ClientConnector, Sizeable, Serializable { public void setIcon(Resource icon); /** - * Gets the parent window of the component. + * Gets the Root the component is attached to. * * <p> - * If the component is not attached to a window through a component + * If the component is not attached to a Root through a component * containment hierarchy, <code>null</code> is returned. * </p> * - * <p> - * The window can be either an application-level window or a sub-window. If - * the component is itself a window, it returns a reference to itself, not - * to its containing window (of a sub-window). - * </p> - * - * @return the parent window of the component or <code>null</code> if it is - * not attached to a window or is itself a window + * @return the Root of the component or <code>null</code> if it is not + * attached to a Root */ public Root getRoot(); @@ -551,11 +514,16 @@ public interface Component extends ClientConnector, Sizeable, Serializable { * * <p> * The method will return {@code null} if the component is not currently - * attached to an application. This is often a problem in constructors of - * regular components and in the initializers of custom composite - * components. A standard workaround is to move the problematic - * initialization to {@link #attach()}, as described in the documentation of - * the method. + * attached to an application. + * </p> + * + * <p> + * Getting a null value is often a problem in constructors of regular + * components and in the initializers of custom composite components. A + * standard workaround is to use {@link Application#getCurrent()} + * to retrieve the application instance that the current request relates to. + * Another way is to move the problematic initialization to + * {@link #attach()}, as described in the documentation of the method. * </p> * * @return the parent application of the component or <code>null</code>. @@ -564,15 +532,7 @@ public interface Component extends ClientConnector, Sizeable, Serializable { public Application getApplication(); /** - * Notifies the component that it is connected to an application. - * - * <p> - * The caller of this method is {@link #setParent(Component)} if the parent - * is itself already attached to the application. If not, the parent will - * call the {@link #attach()} for all its children when it is attached to - * the application. This method is always called before the component is - * painted for the first time. - * </p> + * {@inheritDoc} * * <p> * Reimplementing the {@code attach()} method is useful for tasks that need @@ -624,38 +584,10 @@ public interface Component extends ClientConnector, Sizeable, Serializable { * } * } * </pre> - * - * <p> - * The attachment logic is implemented in {@link AbstractComponent}. - * </p> - * - * @see #getApplication() */ public void attach(); /** - * Notifies the component that it is detached from the application. - * - * <p> - * The {@link #getApplication()} and {@link #getRoot()} methods might return - * <code>null</code> after this method is called. - * </p> - * - * <p> - * This method must call {@link Root#componentDetached(Component)} to let - * the Root know that a new Component has been attached. - * </p> - * * - * <p> - * The caller of this method is {@link #setParent(Component)} if the parent - * is in the application. When the parent is detached from the application - * it is its response to call {@link #detach()} for all the children and to - * detach itself from the terminal. - * </p> - */ - public void detach(); - - /** * Gets the locale of the component. * * <p> @@ -719,75 +651,6 @@ public interface Component extends ClientConnector, Sizeable, Serializable { */ public String getDebugId(); - /** - * Requests that the component should be repainted as soon as possible. - */ - public void requestRepaint(); - - /** - * Repaint request event is thrown when the connector 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. - */ - @SuppressWarnings("serial") - public static class RepaintRequestEvent extends EventObject { - - /** - * Constructs a new event. - * - * @param source - * the paintable needing repaint. - */ - public RepaintRequestEvent(ClientConnector source) { - super(source); - } - - /** - * Gets the connector needing repainting. - * - * @return Paintable for which the <code>paint</code> method will return - * dissimilar UIDL from the previous call of the method. - */ - public ClientConnector getConnector() { - return (ClientConnector) 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 extends Serializable { - - /** - * 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); - /* Component event framework */ /** diff --git a/src/com/vaadin/ui/ConnectorTracker.java b/src/com/vaadin/ui/ConnectorTracker.java new file mode 100644 index 0000000000..75a75ad22a --- /dev/null +++ b/src/com/vaadin/ui/ConnectorTracker.java @@ -0,0 +1,229 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.ui; + +import java.io.Serializable; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.vaadin.terminal.AbstractClientConnector; +import com.vaadin.terminal.gwt.client.ServerConnector; +import com.vaadin.terminal.gwt.server.ClientConnector; + +/** + * A class which takes care of book keeping of {@link ClientConnector}s for one + * Root. + * <p> + * Provides {@link #getConnector(String)} which can be used to lookup a + * connector from its id. This is for framework use only and should not be + * needed in applications. + * </p> + * <p> + * Tracks which {@link ClientConnector}s are dirty so they can be updated to the + * client when the following response is sent. A connector is dirty when an + * operation has been performed on it on the server and as a result of this + * operation new information needs to be sent to its {@link ServerConnector}. + * </p> + * + * @author Vaadin Ltd + * @version @VERSION@ + * @since 7.0.0 + * + */ +public class ConnectorTracker implements Serializable { + + private final HashMap<String, ClientConnector> connectorIdToConnector = new HashMap<String, ClientConnector>(); + private Set<ClientConnector> dirtyConnectors = new HashSet<ClientConnector>(); + + private Root root; + + /** + * Gets a logger for this class + * + * @return A logger instance for logging within this class + * + */ + public static Logger getLogger() { + return Logger.getLogger(ConnectorTracker.class.getName()); + } + + public ConnectorTracker(Root root) { + this.root = root; + } + + /** + * Register the given connector. + * <p> + * The lookup method {@link #getConnector(String)} only returns registered + * connectors. + * </p> + * + * @param connector + * The connector to register. + */ + public void registerConnector(ClientConnector connector) { + String connectorId = connector.getConnectorId(); + ClientConnector previouslyRegistered = connectorIdToConnector + .get(connectorId); + if (previouslyRegistered == null) { + connectorIdToConnector.put(connectorId, connector); + getLogger().fine( + "Registered " + connector.getClass().getSimpleName() + " (" + + connectorId + ")"); + } else if (previouslyRegistered != connector) { + throw new RuntimeException("A connector with id " + connectorId + + " is already registered!"); + } else { + getLogger().warning( + "An already registered connector was registered again: " + + connector.getClass().getSimpleName() + " (" + + connectorId + ")"); + } + + } + + /** + * Unregister the given connector. + * + * <p> + * The lookup method {@link #getConnector(String)} only returns registered + * connectors. + * </p> + * + * @param connector + * The connector to unregister + */ + public void unregisterConnector(ClientConnector connector) { + String connectorId = connector.getConnectorId(); + if (!connectorIdToConnector.containsKey(connectorId)) { + getLogger().warning( + "Tried to unregister " + + connector.getClass().getSimpleName() + " (" + + connectorId + ") which is not registered"); + return; + } + if (connectorIdToConnector.get(connectorId) != connector) { + throw new RuntimeException("The given connector with id " + + connectorId + + " is not the one that was registered for that id"); + } + + getLogger().fine( + "Unregistered " + connector.getClass().getSimpleName() + " (" + + connectorId + ")"); + connectorIdToConnector.remove(connectorId); + } + + /** + * Gets a connector by its id. + * + * @param connectorId + * The connector id to look for + * @return The connector with the given id or null if no connector has the + * given id + */ + public ClientConnector getConnector(String connectorId) { + return connectorIdToConnector.get(connectorId); + } + + /** + * Cleans the connector map from all connectors that are no longer attached + * to the application. This should only be called by the framework. + */ + public void cleanConnectorMap() { + // remove detached components from paintableIdMap so they + // can be GC'ed + Iterator<String> iterator = connectorIdToConnector.keySet().iterator(); + + while (iterator.hasNext()) { + String connectorId = iterator.next(); + ClientConnector connector = connectorIdToConnector.get(connectorId); + if (connector instanceof Component) { + Component component = (Component) connector; + if (component.getRoot() != root) { + // If component is no longer part of this application, + // remove it from the map. If it is re-attached to the + // application at some point it will be re-added through + // registerConnector(connector) + iterator.remove(); + } + } + } + + } + + public void markDirty(ClientConnector connector) { + if (getLogger().isLoggable(Level.FINE)) { + if (!dirtyConnectors.contains(connector)) { + getLogger() + .fine(getDebugInfo(connector) + " " + "is now dirty"); + } + } + + dirtyConnectors.add(connector); + } + + public void markClean(ClientConnector connector) { + if (getLogger().isLoggable(Level.FINE)) { + if (dirtyConnectors.contains(connector)) { + getLogger().fine( + getDebugInfo(connector) + " " + "is no longer dirty"); + } + } + + dirtyConnectors.remove(connector); + } + + private String getDebugInfo(ClientConnector connector) { + String message = getObjectString(connector); + if (connector.getParent() != null) { + message += " (parent: " + getObjectString(connector.getParent()) + + ")"; + } + return message; + } + + private String getObjectString(Object connector) { + return connector.getClass().getName() + "@" + + Integer.toHexString(connector.hashCode()); + } + + public void markAllConnectorsDirty() { + markConnectorsDirtyRecursively(root); + getLogger().fine("All connectors are now dirty"); + } + + public void markAllConnectorsClean() { + dirtyConnectors.clear(); + getLogger().fine("All connectors are now clean"); + } + + /** + * Marks all visible connectors dirty, starting from the given connector and + * going downwards in the hierarchy. + * + * @param c + * The component to start iterating downwards from + */ + private void markConnectorsDirtyRecursively(ClientConnector c) { + if (c instanceof Component && !((Component) c).isVisible()) { + return; + } + markDirty(c); + for (ClientConnector child : AbstractClientConnector + .getAllChildrenIterable(c)) { + markConnectorsDirtyRecursively(child); + } + } + + public Collection<ClientConnector> getDirtyConnectors() { + return dirtyConnectors; + } + +} diff --git a/src/com/vaadin/ui/CssLayout.java b/src/com/vaadin/ui/CssLayout.java index 0a2656af31..e8ec6bd041 100644 --- a/src/com/vaadin/ui/CssLayout.java +++ b/src/com/vaadin/ui/CssLayout.java @@ -11,9 +11,9 @@ import com.vaadin.event.LayoutEvents.LayoutClickListener; import com.vaadin.event.LayoutEvents.LayoutClickNotifier; import com.vaadin.terminal.gwt.client.Connector; import com.vaadin.terminal.gwt.client.MouseEventDetails; +import com.vaadin.terminal.gwt.client.ui.LayoutClickEventHandler; import com.vaadin.terminal.gwt.client.ui.csslayout.CssLayoutServerRpc; import com.vaadin.terminal.gwt.client.ui.csslayout.CssLayoutState; -import com.vaadin.terminal.gwt.client.ui.LayoutClickEventHandler; /** * CssLayout is a layout component that can be used in browser environment only. @@ -185,7 +185,8 @@ public class CssLayout extends AbstractLayout implements LayoutClickNotifier { public void updateState() { super.updateState(); getState().getChildCss().clear(); - for (Component child : this) { + for (Iterator<Component> ci = getComponentIterator(); ci.hasNext();) { + Component child = ci.next(); String componentCssString = getCss(child); if (componentCssString != null) { getState().getChildCss().put(child, componentCssString); diff --git a/src/com/vaadin/ui/CustomField.java b/src/com/vaadin/ui/CustomField.java index 806ee91335..0998c11612 100644 --- a/src/com/vaadin/ui/CustomField.java +++ b/src/com/vaadin/ui/CustomField.java @@ -62,24 +62,18 @@ public abstract class CustomField<T> extends AbstractField<T> implements */ @Override public void attach() { - root = getContent(); + // First call super attach to notify all children (none if content has + // not yet been created) super.attach(); - getContent().setParent(this); - getContent().attach(); - fireComponentAttachEvent(getContent()); - } - - /** - * Notifies the content that the {@link CustomField} is detached from a - * window. - * - * @see com.vaadin.ui.Component#detach() - */ - @Override - public void detach() { - super.detach(); - getContent().detach(); + // If the content has not yet been created, we create and attach it at + // this point. + if (root == null) { + // Ensure content is created and its parent is set. + // The getContent() call creates the content and attaches the + // content + fireComponentAttachEvent(getContent()); + } } /** @@ -90,6 +84,7 @@ public abstract class CustomField<T> extends AbstractField<T> implements protected Component getContent() { if (null == root) { root = initContent(); + root.setParent(this); } return root; } @@ -154,10 +149,6 @@ public abstract class CustomField<T> extends AbstractField<T> implements return (null != getContent()) ? 1 : 0; } - public void requestRepaintAll() { - AbstractComponentContainer.requestRepaintAll(this); - } - /** * Fires the component attached event. This should be called by the * addComponent methods after the component have been added to this diff --git a/src/com/vaadin/ui/DirtyConnectorTracker.java b/src/com/vaadin/ui/DirtyConnectorTracker.java deleted file mode 100644 index 84df7e7c7c..0000000000 --- a/src/com/vaadin/ui/DirtyConnectorTracker.java +++ /dev/null @@ -1,132 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.ui; - -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.vaadin.terminal.gwt.server.AbstractCommunicationManager; -import com.vaadin.terminal.gwt.server.ClientConnector; -import com.vaadin.ui.Component.RepaintRequestEvent; -import com.vaadin.ui.Component.RepaintRequestListener; - -/** - * A class that tracks dirty {@link ClientConnector}s. A {@link ClientConnector} - * is dirty when an operation has been performed on it on the server and as a - * result of this operation new information needs to be sent to its client side - * counterpart. - * - * @author Vaadin Ltd - * @version @VERSION@ - * @since 7.0.0 - * - */ -public class DirtyConnectorTracker implements RepaintRequestListener { - private Set<Component> dirtyComponents = new HashSet<Component>(); - private Root root; - - /** - * Gets a logger for this class - * - * @return A logger instance for logging within this class - * - */ - public static Logger getLogger() { - return Logger.getLogger(DirtyConnectorTracker.class.getName()); - } - - public DirtyConnectorTracker(Root root) { - this.root = root; - } - - public void repaintRequested(RepaintRequestEvent event) { - markDirty((Component) event.getConnector()); - } - - public void componentAttached(Component component) { - component.addListener(this); - markDirty(component); - } - - private void markDirty(Component component) { - if (getLogger().isLoggable(Level.FINE)) { - if (!dirtyComponents.contains(component)) { - getLogger() - .fine(getDebugInfo(component) + " " + "is now dirty"); - } - } - - dirtyComponents.add(component); - } - - private void markClean(Component component) { - if (getLogger().isLoggable(Level.FINE)) { - if (dirtyComponents.contains(component)) { - getLogger().fine( - getDebugInfo(component) + " " + "is no longer dirty"); - } - } - - dirtyComponents.remove(component); - } - - private String getDebugInfo(Component component) { - String message = getObjectString(component); - if (component.getParent() != null) { - message += " (parent: " + getObjectString(component.getParent()) - + ")"; - } - return message; - } - - private String getObjectString(Object component) { - return component.getClass().getName() + "@" - + Integer.toHexString(component.hashCode()); - } - - public void componentDetached(Component component) { - component.removeListener(this); - markClean(component); - } - - public void markAllComponentsDirty() { - markComponentsDirtyRecursively(root); - getLogger().fine("All components are now dirty"); - } - - public void markAllComponentsClean() { - dirtyComponents.clear(); - getLogger().fine("All components are now clean"); - } - - /** - * Marks all visible components dirty, starting from the given component and - * going downwards in the hierarchy. - * - * @param c - * The component to start iterating downwards from - */ - private void markComponentsDirtyRecursively(Component c) { - if (!c.isVisible()) { - return; - } - markDirty(c); - if (c instanceof HasComponents) { - HasComponents container = (HasComponents) c; - for (Component child : AbstractCommunicationManager - .getChildComponents(container)) { - markComponentsDirtyRecursively(child); - } - } - - } - - public Collection<Component> getDirtyComponents() { - return dirtyComponents; - } - -} diff --git a/src/com/vaadin/ui/Form.java b/src/com/vaadin/ui/Form.java index 5f5516b21f..796ad17448 100644 --- a/src/com/vaadin/ui/Form.java +++ b/src/com/vaadin/ui/Form.java @@ -968,34 +968,6 @@ public class Form extends AbstractField<Object> implements Item.Editor, } /** - * Notifies the component that it is connected to an application - * - * @see com.vaadin.ui.Component#attach() - */ - @Override - public void attach() { - super.attach(); - getLayout().attach(); - if (getFooter() != null) { - getFooter().attach(); - } - } - - /** - * Notifies the component that it is detached from the application. - * - * @see com.vaadin.ui.Component#detach() - */ - @Override - public void detach() { - super.detach(); - getLayout().detach(); - if (getFooter() != null) { - getFooter().detach(); - } - } - - /** * Checks the validity of the Form and all of its fields. * * @see com.vaadin.data.Validatable#validate() @@ -1411,10 +1383,6 @@ public class Form extends AbstractField<Object> implements Item.Editor, return new ComponentIterator(); } - public void requestRepaintAll() { - AbstractComponentContainer.requestRepaintAll(this); - } - public int getComponentCount() { int count = 0; if (getLayout() != null) { diff --git a/src/com/vaadin/ui/HasComponents.java b/src/com/vaadin/ui/HasComponents.java index eca89ddcd2..3ebd63bff2 100644 --- a/src/com/vaadin/ui/HasComponents.java +++ b/src/com/vaadin/ui/HasComponents.java @@ -21,7 +21,10 @@ public interface HasComponents extends Component, Iterable<Component> { * container. * * @return the component iterator. + * + * @deprecated Use {@link #iterator()} instead. */ + @Deprecated public Iterator<Component> getComponentIterator(); /** @@ -43,12 +46,4 @@ public interface HasComponents extends Component, Iterable<Component> { */ public boolean isComponentVisible(Component childComponent); - /** - * Causes a repaint of this component, and all components below it. - * - * This should only be used in special cases, e.g when the state of a - * descendant depends on the state of a ancestor. - */ - public void requestRepaintAll(); - } diff --git a/src/com/vaadin/ui/HelloWorldExtension.java b/src/com/vaadin/ui/HelloWorldExtension.java new file mode 100644 index 0000000000..e705954f2e --- /dev/null +++ b/src/com/vaadin/ui/HelloWorldExtension.java @@ -0,0 +1,38 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.ui; + +import com.vaadin.terminal.AbstractExtension; +import com.vaadin.terminal.gwt.client.ui.helloworldfeature.GreetAgainRpc; +import com.vaadin.terminal.gwt.client.ui.helloworldfeature.HelloWorldRpc; +import com.vaadin.terminal.gwt.client.ui.helloworldfeature.HelloWorldState; + +public class HelloWorldExtension extends AbstractExtension { + + public HelloWorldExtension() { + registerRpc(new HelloWorldRpc() { + public void onMessageSent(String message) { + Notification.show(message); + } + }); + } + + @Override + public HelloWorldState getState() { + return (HelloWorldState) super.getState(); + } + + public void setGreeting(String greeting) { + getState().setGreeting(greeting); + requestRepaint(); + } + + public String getGreeting() { + return getState().getGreeting(); + } + + public void greetAgain() { + getRpcProxy(GreetAgainRpc.class).greetAgain(); + } +} diff --git a/src/com/vaadin/ui/JavaScript.java b/src/com/vaadin/ui/JavaScript.java new file mode 100644 index 0000000000..d256717711 --- /dev/null +++ b/src/com/vaadin/ui/JavaScript.java @@ -0,0 +1,146 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.ui; + +import java.util.HashMap; +import java.util.Map; + +import com.vaadin.external.json.JSONArray; +import com.vaadin.external.json.JSONException; +import com.vaadin.terminal.AbstractExtension; +import com.vaadin.terminal.Page; +import com.vaadin.terminal.gwt.client.communication.ServerRpc; +import com.vaadin.terminal.gwt.client.extensions.javascriptmanager.ExecuteJavaScriptRpc; +import com.vaadin.terminal.gwt.client.extensions.javascriptmanager.JavaScriptManagerState; + +/** + * Provides access to JavaScript functionality in the web browser. To get an + * instance of JavaScript, either use Page.getJavaScript() or + * JavaScript.getCurrent() as a shorthand for getting the JavaScript object + * corresponding to the current Page. + * + * @author Vaadin Ltd + * @version @VERSION@ + * @since 7.0.0 + */ +public class JavaScript extends AbstractExtension { + private Map<String, JavaScriptCallback> callbacks = new HashMap<String, JavaScriptCallback>(); + + // Can not be defined in client package as this JSONArray is not available + // in GWT + public interface JavaScriptCallbackRpc extends ServerRpc { + public void call(String name, JSONArray arguments); + } + + /** + * Creates a new JavaScript object. You should typically not this, but + * instead use the JavaScript object already associated with your Page + * object. + */ + public JavaScript() { + registerRpc(new JavaScriptCallbackRpc() { + public void call(String name, JSONArray arguments) { + JavaScriptCallback callback = callbacks.get(name); + // TODO handle situation if name is not registered + try { + callback.call(arguments); + } catch (JSONException e) { + throw new IllegalArgumentException(e); + } + } + }); + } + + @Override + public JavaScriptManagerState getState() { + return (JavaScriptManagerState) super.getState(); + } + + /** + * Add a new function to the global JavaScript namespace (i.e. the window + * object). The <code>call</code> method in the passed + * {@link JavaScriptCallback} object will be invoked with the same + * parameters whenever the JavaScript function is called in the browser. + * + * A callback added with the name <code>"myCallback"</code> can thus be + * invoked with the following JavaScript code: + * <code>window.myCallback(argument1, argument2)</code>. + * + * If the name parameter contains dots, simple objects are created on demand + * to allow calling the function using the same name (e.g. + * <code>window.myObject.myFunction</code>). + * + * @param name + * the name that the callback function should get in the global + * JavaScript namespace. + * @param callback + * the JavaScriptCallback that will be invoked if the JavaScript + * function is called. + */ + public void addCallback(String name, JavaScriptCallback callback) { + callbacks.put(name, callback); + if (getState().getNames().add(name)) { + requestRepaint(); + } + } + + /** + * Removes a JavaScripCallback from the browser's global JavaScript + * namespace. + * + * If the name contains dots and intermediate were created by + * {@link #addCallback(String, JavaScriptCallback)}addCallback, these + * objects will not be removed when the callback is removed. + * + * @param name + * the name of the callback to remove + */ + public void removeCallback(String name) { + callbacks.remove(name); + if (getState().getNames().remove(name)) { + requestRepaint(); + } + } + + /** + * Executes the given JavaScript code in the browser. + * + * @param script + * The JavaScript code to run. + */ + public void execute(String script) { + getRpcProxy(ExecuteJavaScriptRpc.class).executeJavaScript(script); + } + + /** + * Get the JavaScript object for the current Page, or null if there is no + * current page. + * + * @see Page#getCurrent() + * + * @return the JavaScript object corresponding to the current Page, or + * <code>null</code> if there is no current page. + */ + public static JavaScript getCurrent() { + Page page = Page.getCurrent(); + if (page == null) { + return null; + } + return page.getJavaScript(); + } + + /** + * JavaScript is not designed to be removed. + * + * @throws UnsupportedOperationException + * when invoked + */ + @Override + public void removeFromTarget() { + throw new UnsupportedOperationException( + "JavaScript is not designed to be removed."); + } + +} diff --git a/src/com/vaadin/ui/JavaScriptCallback.java b/src/com/vaadin/ui/JavaScriptCallback.java new file mode 100644 index 0000000000..49f7695e89 --- /dev/null +++ b/src/com/vaadin/ui/JavaScriptCallback.java @@ -0,0 +1,41 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.ui; + +import java.io.Serializable; + +import com.vaadin.external.json.JSONArray; +import com.vaadin.external.json.JSONException; +import com.vaadin.terminal.AbstractJavaScriptExtension; + +/** + * Defines a method that is called by a client-side JavaScript function. When + * the corresponding JavaScript function is called, the {@link #call(JSONArray)} + * method is invoked. + * + * @see JavaScript#addCallback(String, JavaScriptCallback) + * @see AbstractJavaScriptComponent#registerCallback(String, JavaScriptCallback) + * @see AbstractJavaScriptExtension#registerCallback(String, JavaScriptCallback) + * + * @author Vaadin Ltd + * @version @VERSION@ + * @since 7.0.0 + */ +public interface JavaScriptCallback extends Serializable { + /** + * Invoked whenever the corresponding JavaScript function is called in the + * browser. + * <p> + * Because of the asynchronous nature of the communication between client + * and server, no return value can be sent back to the browser. + * + * @param arguments + * an array with JSON representations of the arguments with which + * the JavaScript function was called. + * @throws JSONException + * if the arguments can not be interpreted + */ + public void call(JSONArray arguments) throws JSONException; +} diff --git a/src/com/vaadin/ui/Label.java b/src/com/vaadin/ui/Label.java index 99a0f89e5c..e1c64605d7 100644 --- a/src/com/vaadin/ui/Label.java +++ b/src/com/vaadin/ui/Label.java @@ -7,7 +7,8 @@ package com.vaadin.ui; import java.lang.reflect.Method; import com.vaadin.data.Property; -import com.vaadin.data.util.ObjectProperty; +import com.vaadin.data.util.converter.Converter; +import com.vaadin.data.util.converter.ConverterUtil; import com.vaadin.terminal.gwt.client.ui.label.ContentMode; import com.vaadin.terminal.gwt.client.ui.label.LabelState; @@ -36,10 +37,9 @@ import com.vaadin.terminal.gwt.client.ui.label.LabelState; * @since 3.0 */ @SuppressWarnings("serial") -// TODO generics for interface Property -public class Label extends AbstractComponent implements Property, +public class Label extends AbstractComponent implements Property<String>, Property.Viewer, Property.ValueChangeListener, - Property.ValueChangeNotifier, Comparable<Object> { + Property.ValueChangeNotifier, Comparable<Label> { /** * @deprecated From 7.0, use {@link ContentMode#TEXT} instead @@ -77,9 +77,13 @@ public class Label extends AbstractComponent implements Property, @Deprecated public static final ContentMode CONTENT_DEFAULT = ContentMode.TEXT; - private static final String DATASOURCE_MUST_BE_SET = "Datasource must be set"; + /** + * A converter used to convert from the data model type to the field type + * and vice versa. Label type is always String. + */ + private Converter<String, Object> converter = null; - private Property dataSource; + private Property<String> dataSource = null; /** * Creates an empty Label. @@ -114,7 +118,9 @@ public class Label extends AbstractComponent implements Property, * @param contentMode */ public Label(String content, ContentMode contentMode) { - this(new ObjectProperty<String>(content, String.class), contentMode); + setValue(content); + setContentMode(contentMode); + setWidth(100, Unit.PERCENTAGE); } /** @@ -127,15 +133,7 @@ public class Label extends AbstractComponent implements Property, public Label(Property contentSource, ContentMode contentMode) { setPropertyDataSource(contentSource); setContentMode(contentMode); - setWidth(100, UNITS_PERCENTAGE); - } - - @Override - public void updateState() { - super.updateState(); - // We don't know when the text is updated so update it here before - // sending the state to the client - getState().setText(getStringValue()); + setWidth(100, Unit.PERCENTAGE); } @Override @@ -149,25 +147,35 @@ public class Label extends AbstractComponent implements Property, * * @return the Value of the label. */ - public Object getValue() { - if (dataSource == null) { - throw new IllegalStateException(DATASOURCE_MUST_BE_SET); + public String getValue() { + if (getPropertyDataSource() == null) { + // Use internal value if we are running without a data source + return getState().getText(); } - return dataSource.getValue(); + return ConverterUtil.convertFromModel(getPropertyDataSource() + .getValue(), String.class, getConverter(), getLocale()); } /** * Set the value of the label. Value of the label is the XML contents of the * label. * - * @param newValue + * @param newStringValue * the New value of the label. */ - public void setValue(Object newValue) { - if (dataSource == null) { - throw new IllegalStateException(DATASOURCE_MUST_BE_SET); + public void setValue(Object newStringValue) { + if (newStringValue != null && newStringValue.getClass() != String.class) { + throw new Converter.ConversionException("Value of type " + + newStringValue.getClass() + " cannot be assigned to " + + String.class.getName()); + } + if (getPropertyDataSource() == null) { + getState().setText((String) newStringValue); + requestRepaint(); + } else { + throw new IllegalStateException( + "Label is only a Property.Viewer and cannot update its data source"); } - dataSource.setValue(newValue); } /** @@ -179,26 +187,7 @@ public class Label extends AbstractComponent implements Property, @Override public String toString() { throw new UnsupportedOperationException( - "Use Property.getValue() instead of Label.toString()"); - } - - /** - * Returns the value of the <code>Property</code> in human readable textual - * format. - * - * This method exists to help migration from previous Vaadin versions by - * providing a simple replacement for {@link #toString()}. However, it is - * normally better to use the value of the label directly. - * - * @return String representation of the value stored in the Property - * @since 7.0 - */ - public String getStringValue() { - if (dataSource == null) { - throw new IllegalStateException(DATASOURCE_MUST_BE_SET); - } - Object value = dataSource.getValue(); - return (null != value) ? value.toString() : null; + "Use getValue() instead of Label.toString()"); } /** @@ -206,11 +195,8 @@ public class Label extends AbstractComponent implements Property, * * @see com.vaadin.data.Property#getType() */ - public Class getType() { - if (dataSource == null) { - throw new IllegalStateException(DATASOURCE_MUST_BE_SET); - } - return dataSource.getType(); + public Class<String> getType() { + return String.class; } /** @@ -238,7 +224,13 @@ public class Label extends AbstractComponent implements Property, ((Property.ValueChangeNotifier) dataSource).removeListener(this); } - // Sets the new data source + if (!ConverterUtil.canConverterHandle(getConverter(), String.class, + newDataSource.getType())) { + // Try to find a converter + Converter<String, ?> c = ConverterUtil.getConverter(String.class, + newDataSource.getType(), getApplication()); + setConverter(c); + } dataSource = newDataSource; // Listens the new data source if possible @@ -354,7 +346,6 @@ public class Label extends AbstractComponent implements Property, protected void fireValueChange() { // Set the error message fireEvent(new Label.ValueChangeEvent(this)); - requestRepaint(); } /** @@ -363,9 +354,28 @@ public class Label extends AbstractComponent implements Property, * @see com.vaadin.data.Property.ValueChangeListener#valueChange(Property.ValueChangeEvent) */ public void valueChange(Property.ValueChangeEvent event) { + // Update the internal value from the data source + getState().setText(getValue()); + requestRepaint(); + fireValueChange(); } + private String getComparableValue() { + String stringValue = getValue(); + if (stringValue == null) { + stringValue = ""; + } + + if (getContentMode() == ContentMode.XHTML + || getContentMode() == ContentMode.XML) { + return stripTags(stringValue); + } else { + return stringValue; + } + + } + /** * Compares the Label to other objects. * @@ -387,27 +397,10 @@ public class Label extends AbstractComponent implements Property, * less than, equal to, or greater than the specified object. * @see java.lang.Comparable#compareTo(java.lang.Object) */ - public int compareTo(Object other) { - - String thisValue; - String otherValue; + public int compareTo(Label other) { - if (getContentMode() == ContentMode.XML - || getContentMode() == ContentMode.XHTML) { - thisValue = stripTags(getStringValue()); - } else { - thisValue = getStringValue(); - } - - if (other instanceof Label - && (((Label) other).getContentMode() == ContentMode.XML || ((Label) other) - .getContentMode() == ContentMode.XHTML)) { - otherValue = stripTags(((Label) other).getStringValue()); - } else { - // TODO not a good idea - and might assume that Field.toString() - // returns a string representation of the value - otherValue = other.toString(); - } + String thisValue = getComparableValue(); + String otherValue = other.getComparableValue(); return thisValue.compareTo(otherValue); } @@ -443,4 +436,26 @@ public class Label extends AbstractComponent implements Property, return res.toString(); } + /** + * Gets the converter used to convert the property data source value to the + * label value. + * + * @return The converter or null if none is set. + */ + public Converter<String, Object> getConverter() { + return converter; + } + + /** + * Sets the converter used to convert the label value to the property data + * source type. The converter must have a presentation type of String. + * + * @param converter + * The new converter to use. + */ + public void setConverter(Converter<String, ?> converter) { + this.converter = (Converter<String, Object>) converter; + requestRepaint(); + } + } diff --git a/src/com/vaadin/ui/Link.java b/src/com/vaadin/ui/Link.java index ed5ffbba3a..db0dc58e6b 100644 --- a/src/com/vaadin/ui/Link.java +++ b/src/com/vaadin/ui/Link.java @@ -6,6 +6,7 @@ package com.vaadin.ui; import java.util.Map; +import com.vaadin.terminal.Page; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; import com.vaadin.terminal.Resource; @@ -23,13 +24,13 @@ import com.vaadin.terminal.Vaadin6Component; public class Link extends AbstractComponent implements Vaadin6Component { /* Target window border type constant: No window border */ - public static final int TARGET_BORDER_NONE = Root.BORDER_NONE; + public static final int TARGET_BORDER_NONE = Page.BORDER_NONE; /* Target window border type constant: Minimal window border */ - public static final int TARGET_BORDER_MINIMAL = Root.BORDER_MINIMAL; + public static final int TARGET_BORDER_MINIMAL = Page.BORDER_MINIMAL; /* Target window border type constant: Default window border */ - public static final int TARGET_BORDER_DEFAULT = Root.BORDER_DEFAULT; + public static final int TARGET_BORDER_DEFAULT = Page.BORDER_DEFAULT; private Resource resource = null; diff --git a/src/com/vaadin/ui/Notification.java b/src/com/vaadin/ui/Notification.java index bb1f874635..0358283cb4 100644 --- a/src/com/vaadin/ui/Notification.java +++ b/src/com/vaadin/ui/Notification.java @@ -6,6 +6,7 @@ package com.vaadin.ui; import java.io.Serializable; +import com.vaadin.terminal.Page; import com.vaadin.terminal.Resource; /** @@ -318,4 +319,53 @@ public class Notification implements Serializable { public boolean isHtmlContentAllowed() { return htmlContentAllowed; } + + /** + * Shows this notification on a Page. + * + * @param page + * The page on which the notification should be shown + */ + public void show(Page page) { + // TODO Can avoid deprecated API when Notification extends Extension + page.showNotification(this); + } + + /** + * Shows a notification message on the middle of the current page. The + * message automatically disappears ("humanized message"). + * + * Care should be taken to to avoid XSS vulnerabilities as the caption is + * rendered as html. + * + * @see #Notification(String) + * @see #show(Page) + * + * @param caption + * The message + */ + public static void show(String caption) { + new Notification(caption).show(Page.getCurrent()); + } + + /** + * Shows a notification message the current page. The position and behavior + * of the message depends on the type, which is one of the basic types + * defined in {@link Notification}, for instance + * Notification.TYPE_WARNING_MESSAGE. + * + * Care should be taken to to avoid XSS vulnerabilities as the caption is + * rendered as html. + * + * @see #Notification(String, int) + * @see #show(Page) + * + * @param caption + * The message + * @param type + * The message type + */ + public static void show(String caption, int type) { + new Notification(caption, type).show(Page.getCurrent()); + } }
\ No newline at end of file diff --git a/src/com/vaadin/ui/Panel.java b/src/com/vaadin/ui/Panel.java index b2916f78c7..c339100cda 100644 --- a/src/com/vaadin/ui/Panel.java +++ b/src/com/vaadin/ui/Panel.java @@ -4,6 +4,7 @@ package com.vaadin.ui; +import java.util.Collections; import java.util.Iterator; import java.util.Map; @@ -164,6 +165,7 @@ public class Panel extends AbstractComponentContainer implements Scrollable, .addListener((ComponentContainer.ComponentDetachListener) this); content = newContent; + requestRepaint(); } /** @@ -192,15 +194,6 @@ public class Panel extends AbstractComponentContainer implements Scrollable, } } - @Override - public void requestRepaintAll() { - // Panel has odd structure, delegate to layout - requestRepaint(); - if (getContent() != null) { - getContent().requestRepaintAll(); - } - } - /** * Adds the component into this container. * @@ -237,7 +230,7 @@ public class Panel extends AbstractComponentContainer implements Scrollable, * @see com.vaadin.ui.ComponentContainer#getComponentIterator() */ public Iterator<Component> getComponentIterator() { - return content.getComponentIterator(); + return Collections.singleton((Component) content).iterator(); } /** @@ -354,35 +347,6 @@ public class Panel extends AbstractComponentContainer implements Scrollable, } /** - * Notifies the component that it is connected to an application. - * - * @see com.vaadin.ui.Component#attach() - */ - @Override - public void attach() { - getRoot().componentAttached(this); - // can't call parent here as this is Panels hierarchy is a hack - requestRepaint(); - if (content != null) { - content.attach(); - } - } - - /** - * Notifies the component that it is detached from the application. - * - * @see com.vaadin.ui.Component#detach() - */ - @Override - public void detach() { - // can't call parent here as this is Panels hierarchy is a hack - if (content != null) { - content.detach(); - } - getRoot().componentDetached(this); - } - - /** * Removes all components from this container. * * @see com.vaadin.ui.ComponentContainer#removeAllComponents() diff --git a/src/com/vaadin/ui/Root.java b/src/com/vaadin/ui/Root.java index 93b98693c2..7ae687be79 100644 --- a/src/com/vaadin/ui/Root.java +++ b/src/com/vaadin/ui/Root.java @@ -4,8 +4,6 @@ package com.vaadin.ui; -import java.io.Serializable; -import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; @@ -13,8 +11,6 @@ import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashSet; -import java.util.LinkedList; -import java.util.List; import java.util.Map; import com.vaadin.Application; @@ -24,6 +20,9 @@ import com.vaadin.event.Action.Handler; import com.vaadin.event.ActionManager; import com.vaadin.event.MouseEvents.ClickEvent; import com.vaadin.event.MouseEvents.ClickListener; +import com.vaadin.terminal.Page; +import com.vaadin.terminal.Page.BrowserWindowResizeEvent; +import com.vaadin.terminal.Page.BrowserWindowResizeListener; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; import com.vaadin.terminal.Resource; @@ -31,11 +30,9 @@ import com.vaadin.terminal.Vaadin6Component; import com.vaadin.terminal.WrappedRequest; import com.vaadin.terminal.WrappedRequest.BrowserDetails; import com.vaadin.terminal.gwt.client.MouseEventDetails; -import com.vaadin.terminal.gwt.client.ui.notification.VNotification; import com.vaadin.terminal.gwt.client.ui.root.RootServerRpc; import com.vaadin.terminal.gwt.client.ui.root.RootState; import com.vaadin.terminal.gwt.client.ui.root.VRoot; -import com.vaadin.tools.ReflectTools; import com.vaadin.ui.Window.CloseListener; /** @@ -79,121 +76,6 @@ public abstract class Root extends AbstractComponentContainer implements Action.Container, Action.Notifier, Vaadin6Component { /** - * Listener that gets notified when the size of the browser window - * containing the root has changed. - * - * @see Root#addListener(BrowserWindowResizeListener) - */ - public interface BrowserWindowResizeListener extends Serializable { - /** - * Invoked when the browser window containing a Root has been resized. - * - * @param event - * a browser window resize event - */ - public void browserWindowResized(BrowserWindowResizeEvent event); - } - - /** - * Event that is fired when a browser window containing a root is resized. - */ - public class BrowserWindowResizeEvent extends Component.Event { - - private final int width; - private final int height; - - /** - * Creates a new event - * - * @param source - * the root for which the browser window has been resized - * @param width - * the new width of the browser window - * @param height - * the new height of the browser window - */ - public BrowserWindowResizeEvent(Root source, int width, int height) { - super(source); - this.width = width; - this.height = height; - } - - @Override - public Root getSource() { - return (Root) super.getSource(); - } - - /** - * Gets the new browser window height - * - * @return an integer with the new pixel height of the browser window - */ - public int getHeight() { - return height; - } - - /** - * Gets the new browser window width - * - * @return an integer with the new pixel width of the browser window - */ - public int getWidth() { - return width; - } - } - - private static final Method BROWSWER_RESIZE_METHOD = ReflectTools - .findMethod(BrowserWindowResizeListener.class, - "browserWindowResized", BrowserWindowResizeEvent.class); - - /** - * Listener that listens changes in URI fragment. - */ - public interface FragmentChangedListener extends Serializable { - public void fragmentChanged(FragmentChangedEvent event); - } - - /** - * Event fired when uri fragment changes. - */ - public class FragmentChangedEvent extends Component.Event { - - /** - * The new uri fragment - */ - private final String fragment; - - /** - * Creates a new instance of UriFragmentReader change event. - * - * @param source - * the Source of the event. - */ - public FragmentChangedEvent(Root source, String fragment) { - super(source); - this.fragment = fragment; - } - - /** - * Gets the root in which the fragment has changed. - * - * @return the root in which the fragment has changed - */ - public Root getRoot() { - return (Root) getComponent(); - } - - /** - * Get the new fragment - * - * @return the new fragment - */ - public String getFragment() { - return fragment; - } - } - - /** * Helper class to emulate the main window from Vaadin 6 using roots. This * class should be used in the same way as Window used as a browser level * window in Vaadin 6 with {@link com.vaadin.Application.LegacyApplication} @@ -312,55 +194,191 @@ public abstract class Root extends AbstractComponentContainer implements "Internal problem getting window URL, please report"); } } - } - private static final Method FRAGMENT_CHANGED_METHOD; - - static { - try { - FRAGMENT_CHANGED_METHOD = FragmentChangedListener.class - .getDeclaredMethod("fragmentChanged", - new Class[] { FragmentChangedEvent.class }); - } catch (final java.lang.NoSuchMethodException e) { - // This should never happen - throw new java.lang.RuntimeException( - "Internal error finding methods in FragmentChangedListener"); + /** + * Opens the given resource in this root. The contents of this Root is + * replaced by the {@code Resource}. + * + * @param resource + * the resource to show in this root + * + * @deprecated As of 7.0, use getPage().open instead + */ + @Deprecated + public void open(Resource resource) { + getPage().open(resource); + } + + /* ********************************************************************* */ + + /** + * Opens the given resource in a window with the given name. + * <p> + * The supplied {@code windowName} is used as the target name in a + * window.open call in the client. This means that special values such + * as "_blank", "_self", "_top", "_parent" have special meaning. An + * empty or <code>null</code> window name is also a special case. + * </p> + * <p> + * "", null and "_self" as {@code windowName} all causes the resource to + * be opened in the current window, replacing any old contents. For + * downloadable content you should avoid "_self" as "_self" causes the + * client to skip rendering of any other changes as it considers them + * irrelevant (the page will be replaced by the resource). This can + * speed up the opening of a resource, but it might also put the client + * side into an inconsistent state if the window content is not + * completely replaced e.g., if the resource is downloaded instead of + * displayed in the browser. + * </p> + * <p> + * "_blank" as {@code windowName} causes the resource to always be + * opened in a new window or tab (depends on the browser and browser + * settings). + * </p> + * <p> + * "_top" and "_parent" as {@code windowName} works as specified by the + * HTML standard. + * </p> + * <p> + * Any other {@code windowName} will open the resource in a window with + * that name, either by opening a new window/tab in the browser or by + * replacing the contents of an existing window with that name. + * </p> + * + * @param resource + * the resource. + * @param windowName + * the name of the window. + * @deprecated As of 7.0, use getPage().open instead + */ + @Deprecated + public void open(Resource resource, String windowName) { + getPage().open(resource, windowName); } - } - /** - * A border style used for opening resources in a window without a border. - */ - public static final int BORDER_NONE = 0; + /** + * Opens the given resource in a window with the given size, border and + * name. For more information on the meaning of {@code windowName}, see + * {@link #open(Resource, String)}. + * + * @param resource + * the resource. + * @param windowName + * the name of the window. + * @param width + * the width of the window in pixels + * @param height + * the height of the window in pixels + * @param border + * the border style of the window. See {@link #BORDER_NONE + * Window.BORDER_* constants} + * @deprecated As of 7.0, use getPage().open instead + */ + @Deprecated + public void open(Resource resource, String windowName, int width, + int height, int border) { + getPage().open(resource, windowName, width, height, border); + } - /** - * A border style used for opening resources in a window with a minimal - * border. - */ - public static final int BORDER_MINIMAL = 1; + /** + * Adds a new {@link BrowserWindowResizeListener} to this root. The + * listener will be notified whenever the browser window within which + * this root resides is resized. + * + * @param resizeListener + * the listener to add + * + * @see BrowserWindowResizeListener#browserWindowResized(BrowserWindowResizeEvent) + * @see #setResizeLazy(boolean) + * + * @deprecated As of 7.0, use the similarly named api in Page instead + */ + @Deprecated + public void addListener(BrowserWindowResizeListener resizeListener) { + getPage().addListener(resizeListener); + } - /** - * A border style that indicates that the default border style should be - * used when opening resources. - */ - public static final int BORDER_DEFAULT = 2; + /** + * Removes a {@link BrowserWindowResizeListener} from this root. The + * listener will no longer be notified when the browser window is + * resized. + * + * @param resizeListener + * the listener to remove + * @deprecated As of 7.0, use the similarly named api in Page instead + */ + @Deprecated + public void removeListener(BrowserWindowResizeListener resizeListener) { + getPage().removeListener(resizeListener); + } - /** - * The application to which this root belongs - */ - private Application application; + /** + * Gets the last known height of the browser window in which this root + * resides. + * + * @return the browser window height in pixels + * @deprecated As of 7.0, use the similarly named api in Page instead + */ + @Deprecated + public int getBrowserWindowHeight() { + return getPage().getBrowserWindowHeight(); + } - /** - * A list of notifications that are waiting to be sent to the client. - * Cleared (set to null) when the notifications have been sent. - */ - private List<Notification> notifications; + /** + * Gets the last known width of the browser window in which this root + * resides. + * + * @return the browser window width in pixels + * + * @deprecated As of 7.0, use the similarly named api in Page instead + */ + @Deprecated + public int getBrowserWindowWidth() { + return getPage().getBrowserWindowWidth(); + } + + /** + * Executes JavaScript in this window. + * + * <p> + * This method allows one to inject javascript from the server to + * client. A client implementation is not required to implement this + * functionality, but currently all web-based clients do implement this. + * </p> + * + * <p> + * Executing javascript this way often leads to cross-browser + * compatibility issues and regressions that are hard to resolve. Use of + * this method should be avoided and instead it is recommended to create + * new widgets with GWT. For more info on creating own, reusable + * client-side widgets in Java, read the corresponding chapter in Book + * of Vaadin. + * </p> + * + * @param script + * JavaScript snippet that will be executed. + * + * @deprecated as of 7.0, use JavaScript.getCurrent().execute(String) + * instead + */ + @Deprecated + public void executeJavaScript(String script) { + getPage().getJavaScript().execute(script); + } + + @Override + public void setCaption(String caption) { + // Override to provide backwards compatibility + getState().setCaption(caption); + getPage().setTitle(caption); + } + + } /** - * A list of javascript commands that are waiting to be sent to the client. - * Cleared (set to null) when the commands have been sent. + * The application to which this root belongs */ - private List<String> jsExecQueue = null; + private Application application; /** * List of windows in this root. @@ -368,12 +386,6 @@ public abstract class Root extends AbstractComponentContainer implements private final LinkedHashSet<Window> windows = new LinkedHashSet<Window>(); /** - * Resources to be opened automatically on next repaint. The list is - * automatically cleared when it has been sent to the client. - */ - private final LinkedList<OpenResource> openList = new LinkedList<OpenResource>(); - - /** * The component that should be scrolled into view after the next repaint. * Null if nothing should be scrolled into view. */ @@ -399,14 +411,12 @@ public abstract class Root extends AbstractComponentContainer implements */ private static final ThreadLocal<Root> currentRoot = new ThreadLocal<Root>(); - private int browserWindowWidth = -1; - private int browserWindowHeight = -1; - /** Identifies the click event */ private static final String CLICK_EVENT_ID = VRoot.CLICK_EVENT_ID; - private DirtyConnectorTracker dirtyConnectorTracker = new DirtyConnectorTracker( - this); + private ConnectorTracker connectorTracker = new ConnectorTracker(this); + + private Page page = new Page(this); private RootServerRpc rpc = new RootServerRpc() { public void click(MouseEventDetails mouseDetails) { @@ -502,68 +512,7 @@ public abstract class Root extends AbstractComponentContainer implements } public void paintContent(PaintTarget target) throws PaintException { - // Open requested resource - synchronized (openList) { - if (!openList.isEmpty()) { - for (final Iterator<OpenResource> i = openList.iterator(); i - .hasNext();) { - (i.next()).paintContent(target); - } - openList.clear(); - } - } - - // Paint notifications - if (notifications != null) { - target.startTag("notifications"); - for (final Iterator<Notification> it = notifications.iterator(); it - .hasNext();) { - final Notification n = it.next(); - target.startTag("notification"); - if (n.getCaption() != null) { - target.addAttribute( - VNotification.ATTRIBUTE_NOTIFICATION_CAPTION, - n.getCaption()); - } - if (n.getDescription() != null) { - target.addAttribute( - VNotification.ATTRIBUTE_NOTIFICATION_MESSAGE, - n.getDescription()); - } - if (n.getIcon() != null) { - target.addAttribute( - VNotification.ATTRIBUTE_NOTIFICATION_ICON, - n.getIcon()); - } - if (!n.isHtmlContentAllowed()) { - target.addAttribute( - VRoot.NOTIFICATION_HTML_CONTENT_NOT_ALLOWED, true); - } - target.addAttribute( - VNotification.ATTRIBUTE_NOTIFICATION_POSITION, - n.getPosition()); - target.addAttribute(VNotification.ATTRIBUTE_NOTIFICATION_DELAY, - n.getDelayMsec()); - if (n.getStyleName() != null) { - target.addAttribute( - VNotification.ATTRIBUTE_NOTIFICATION_STYLE, - n.getStyleName()); - } - target.endTag("notification"); - } - target.endTag("notifications"); - notifications = null; - } - - // Add executable javascripts if needed - if (jsExecQueue != null) { - for (String script : jsExecQueue) { - target.startTag("execJS"); - target.addAttribute("script", script); - target.endTag("execJS"); - } - jsExecQueue = null; - } + page.paintContent(target); if (scrollIntoView != null) { target.addAttribute("scrollTo", scrollIntoView); @@ -584,10 +533,6 @@ public abstract class Root extends AbstractComponentContainer implements actionManager.paintActions(null, target); } - if (fragment != null) { - target.addAttribute(VRoot.FRAGMENT_VARIABLE, fragment); - } - if (isResizeLazy()) { target.addAttribute(VRoot.RESIZE_LAZY, true); } @@ -618,23 +563,14 @@ public abstract class Root extends AbstractComponentContainer implements if (variables.containsKey(VRoot.FRAGMENT_VARIABLE)) { String fragment = (String) variables.get(VRoot.FRAGMENT_VARIABLE); - setFragment(fragment, true); + getPage().setFragment(fragment, true); } - boolean sendResizeEvent = false; - if (variables.containsKey("height")) { - browserWindowHeight = ((Integer) variables.get("height")) - .intValue(); - sendResizeEvent = true; - } - if (variables.containsKey("width")) { - browserWindowWidth = ((Integer) variables.get("width")).intValue(); - sendResizeEvent = true; - } - if (sendResizeEvent) { - fireEvent(new BrowserWindowResizeEvent(this, browserWindowWidth, - browserWindowHeight)); + if (variables.containsKey("height") || variables.containsKey("width")) { + getPage().setBrowserWindowSize((Integer) variables.get("width"), + (Integer) variables.get("height")); } + } /* @@ -643,11 +579,17 @@ public abstract class Root extends AbstractComponentContainer implements * @see com.vaadin.ui.ComponentContainer#getComponentIterator() */ public Iterator<Component> getComponentIterator() { - if (getContent() == null) { - return Collections.EMPTY_LIST.iterator(); + // TODO could directly create some kind of combined iterator instead of + // creating a new ArrayList + ArrayList<Component> components = new ArrayList<Component>(); + + if (getContent() != null) { + components.add(getContent()); } - return Collections.singleton((Component) getContent()).iterator(); + components.addAll(windows); + + return components.iterator(); } /* @@ -656,7 +598,7 @@ public abstract class Root extends AbstractComponentContainer implements * @see com.vaadin.ui.ComponentContainer#getComponentCount() */ public int getComponentCount() { - return getContent() == null ? 0 : 1; + return windows.size() + (getContent() == null ? 0 : 1); } /** @@ -809,11 +751,6 @@ public abstract class Root extends AbstractComponentContainer implements */ private Focusable pendingFocus; - /** - * The current URI fragment. - */ - private String fragment; - private boolean resizeLazy = false; /** @@ -835,175 +772,6 @@ public abstract class Root extends AbstractComponentContainer implements } /** - * Shows a notification message on the middle of the root. The message - * automatically disappears ("humanized message"). - * - * Care should be taken to to avoid XSS vulnerabilities as the caption is - * rendered as html. - * - * @see #showNotification(Notification) - * @see Notification - * - * @param caption - * The message - */ - public void showNotification(String caption) { - addNotification(new Notification(caption)); - } - - /** - * Shows a notification message the root. The position and behavior of the - * message depends on the type, which is one of the basic types defined in - * {@link Notification}, for instance Notification.TYPE_WARNING_MESSAGE. - * - * Care should be taken to to avoid XSS vulnerabilities as the caption is - * rendered as html. - * - * @see #showNotification(Notification) - * @see Notification - * - * @param caption - * The message - * @param type - * The message type - */ - public void showNotification(String caption, int type) { - addNotification(new Notification(caption, type)); - } - - /** - * Shows a notification consisting of a bigger caption and a smaller - * description on the middle of the root. The message automatically - * disappears ("humanized message"). - * - * Care should be taken to to avoid XSS vulnerabilities as the caption and - * description are rendered as html. - * - * @see #showNotification(Notification) - * @see Notification - * - * @param caption - * The caption of the message - * @param description - * The message description - * - */ - public void showNotification(String caption, String description) { - addNotification(new Notification(caption, description)); - } - - /** - * Shows a notification consisting of a bigger caption and a smaller - * description. The position and behavior of the message depends on the - * type, which is one of the basic types defined in {@link Notification}, - * for instance Notification.TYPE_WARNING_MESSAGE. - * - * Care should be taken to to avoid XSS vulnerabilities as the caption and - * description are rendered as html. - * - * @see #showNotification(Notification) - * @see Notification - * - * @param caption - * The caption of the message - * @param description - * The message description - * @param type - * The message type - */ - public void showNotification(String caption, String description, int type) { - addNotification(new Notification(caption, description, type)); - } - - /** - * Shows a notification consisting of a bigger caption and a smaller - * description. The position and behavior of the message depends on the - * type, which is one of the basic types defined in {@link Notification}, - * for instance Notification.TYPE_WARNING_MESSAGE. - * - * Care should be taken to avoid XSS vulnerabilities if html content is - * allowed. - * - * @see #showNotification(Notification) - * @see Notification - * - * @param caption - * The message caption - * @param description - * The message description - * @param type - * The type of message - * @param htmlContentAllowed - * Whether html in the caption and description should be - * displayed as html or as plain text - */ - public void showNotification(String caption, String description, int type, - boolean htmlContentAllowed) { - addNotification(new Notification(caption, description, type, - htmlContentAllowed)); - } - - /** - * Shows a notification message. - * - * @see Notification - * @see #showNotification(String) - * @see #showNotification(String, int) - * @see #showNotification(String, String) - * @see #showNotification(String, String, int) - * - * @param notification - * The notification message to show - */ - public void showNotification(Notification notification) { - addNotification(notification); - } - - /** - * Internal helper method to actually add a notification. - * - * @param notification - * the notification to add - */ - private void addNotification(Notification notification) { - if (notifications == null) { - notifications = new LinkedList<Notification>(); - } - notifications.add(notification); - requestRepaint(); - } - - /** - * Executes JavaScript in this root. - * - * <p> - * This method allows one to inject javascript from the server to client. A - * client implementation is not required to implement this functionality, - * but currently all web-based clients do implement this. - * </p> - * - * <p> - * Executing javascript this way often leads to cross-browser compatibility - * issues and regressions that are hard to resolve. Use of this method - * should be avoided and instead it is recommended to create new widgets - * with GWT. For more info on creating own, reusable client-side widgets in - * Java, read the corresponding chapter in Book of Vaadin. - * </p> - * - * @param script - * JavaScript snippet that will be executed. - */ - public void executeJavaScript(String script) { - if (jsExecQueue == null) { - jsExecQueue = new ArrayList<String>(); - } - - jsExecQueue.add(script); - - requestRepaint(); - } - - /** * Scrolls any component between the component and root to a suitable * position so the component is visible to the user. The given component * must belong to this root. @@ -1072,6 +840,8 @@ public abstract class Root extends AbstractComponentContainer implements if (content != null) { super.addComponent(content); } + + requestRepaint(); } /** @@ -1115,10 +885,7 @@ public abstract class Root extends AbstractComponentContainer implements * the initialization request */ public void doInit(WrappedRequest request) { - BrowserDetails browserDetails = request.getBrowserDetails(); - if (browserDetails != null) { - fragment = browserDetails.getUriFragment(); - } + getPage().init(request); // Call the init overridden by the application developer init(request); @@ -1156,10 +923,10 @@ public abstract class Root extends AbstractComponentContainer implements * @param root * the root to register as the current root * - * @see #getCurrentRoot() + * @see #getCurrent() * @see ThreadLocal */ - public static void setCurrentRoot(Root root) { + public static void setCurrent(Root root) { currentRoot.set(root); } @@ -1171,192 +938,12 @@ public abstract class Root extends AbstractComponentContainer implements * @return the current root instance if available, otherwise * <code>null</code> * - * @see #setCurrentRoot(Root) + * @see #setCurrent(Root) */ - public static Root getCurrentRoot() { + public static Root getCurrent() { return currentRoot.get(); } - /** - * Opens the given resource in this root. The contents of this Root is - * replaced by the {@code Resource}. - * - * @param resource - * the resource to show in this root - */ - public void open(Resource resource) { - synchronized (openList) { - if (!openList.contains(resource)) { - openList.add(new OpenResource(resource, null, -1, -1, - BORDER_DEFAULT)); - } - } - requestRepaint(); - } - - /* ********************************************************************* */ - - /** - * Opens the given resource in a window with the given name. - * <p> - * The supplied {@code windowName} is used as the target name in a - * window.open call in the client. This means that special values such as - * "_blank", "_self", "_top", "_parent" have special meaning. An empty or - * <code>null</code> window name is also a special case. - * </p> - * <p> - * "", null and "_self" as {@code windowName} all causes the resource to be - * opened in the current window, replacing any old contents. For - * downloadable content you should avoid "_self" as "_self" causes the - * client to skip rendering of any other changes as it considers them - * irrelevant (the page will be replaced by the resource). This can speed up - * the opening of a resource, but it might also put the client side into an - * inconsistent state if the window content is not completely replaced e.g., - * if the resource is downloaded instead of displayed in the browser. - * </p> - * <p> - * "_blank" as {@code windowName} causes the resource to always be opened in - * a new window or tab (depends on the browser and browser settings). - * </p> - * <p> - * "_top" and "_parent" as {@code windowName} works as specified by the HTML - * standard. - * </p> - * <p> - * Any other {@code windowName} will open the resource in a window with that - * name, either by opening a new window/tab in the browser or by replacing - * the contents of an existing window with that name. - * </p> - * - * @param resource - * the resource. - * @param windowName - * the name of the window. - */ - public void open(Resource resource, String windowName) { - synchronized (openList) { - if (!openList.contains(resource)) { - openList.add(new OpenResource(resource, windowName, -1, -1, - BORDER_DEFAULT)); - } - } - requestRepaint(); - } - - /** - * Opens the given resource in a window with the given size, border and - * name. For more information on the meaning of {@code windowName}, see - * {@link #open(Resource, String)}. - * - * @param resource - * the resource. - * @param windowName - * the name of the window. - * @param width - * the width of the window in pixels - * @param height - * the height of the window in pixels - * @param border - * the border style of the window. See {@link #BORDER_NONE - * Window.BORDER_* constants} - */ - public void open(Resource resource, String windowName, int width, - int height, int border) { - synchronized (openList) { - if (!openList.contains(resource)) { - openList.add(new OpenResource(resource, windowName, width, - height, border)); - } - } - requestRepaint(); - } - - /** - * Private class for storing properties related to opening resources. - */ - private class OpenResource implements Serializable { - - /** - * The resource to open - */ - private final Resource resource; - - /** - * The name of the target window - */ - private final String name; - - /** - * The width of the target window - */ - private final int width; - - /** - * The height of the target window - */ - private final int height; - - /** - * The border style of the target window - */ - private final int border; - - /** - * Creates a new open resource. - * - * @param resource - * The resource to open - * @param name - * The name of the target window - * @param width - * The width of the target window - * @param height - * The height of the target window - * @param border - * The border style of the target window - */ - private OpenResource(Resource resource, String name, int width, - int height, int border) { - this.resource = resource; - this.name = name; - this.width = width; - this.height = height; - this.border = border; - } - - /** - * Paints the open request. Should be painted inside the window. - * - * @param target - * the paint target - * @throws PaintException - * if the paint operation fails - */ - private void paintContent(PaintTarget target) throws PaintException { - target.startTag("open"); - target.addAttribute("src", resource); - if (name != null && name.length() > 0) { - target.addAttribute("name", name); - } - if (width >= 0) { - target.addAttribute("width", width); - } - if (height >= 0) { - target.addAttribute("height", height); - } - switch (border) { - case BORDER_MINIMAL: - target.addAttribute("border", "minimal"); - break; - case BORDER_NONE: - target.addAttribute("border", "none"); - break; - } - - target.endTag("open"); - } - } - public void setScrollTop(int scrollTop) { throw new RuntimeException("Not yet implemented"); } @@ -1444,150 +1031,176 @@ public abstract class Root extends AbstractComponentContainer implements removeListener(CLICK_EVENT_ID, ClickEvent.class, listener); } - public void addListener(FragmentChangedListener listener) { - addListener(FragmentChangedEvent.class, listener, - FRAGMENT_CHANGED_METHOD); + @Override + public boolean isConnectorEnabled() { + // TODO How can a Root be invisible? What does it mean? + return isVisible() && isEnabled(); } - public void removeListener(FragmentChangedListener listener) { - removeListener(FragmentChangedEvent.class, listener, - FRAGMENT_CHANGED_METHOD); + public ConnectorTracker getConnectorTracker() { + return connectorTracker; } - /** - * Sets URI fragment. Optionally fires a {@link FragmentChangedEvent} - * - * @param newFragment - * id of the new fragment - * @param fireEvent - * true to fire event - * @see FragmentChangedEvent - * @see FragmentChangedListener - */ - public void setFragment(String newFragment, boolean fireEvents) { - if (newFragment == null) { - throw new NullPointerException("The fragment may not be null"); - } - if (!newFragment.equals(fragment)) { - fragment = newFragment; - if (fireEvents) { - fireEvent(new FragmentChangedEvent(this, newFragment)); - } - requestRepaint(); - } + public Page getPage() { + return page; } /** - * Sets URI fragment. This method fires a {@link FragmentChangedEvent} + * Setting the caption of a Root is not supported. To set the title of the + * HTML page, use Page.setTitle * - * @param newFragment - * id of the new fragment - * @see FragmentChangedEvent - * @see FragmentChangedListener + * @deprecated as of 7.0.0, use {@link Page#setTitle(String)} */ - public void setFragment(String newFragment) { - setFragment(newFragment, true); + @Override + @Deprecated + public void setCaption(String caption) { + throw new IllegalStateException( + "You can not set the title of a Root. To set the title of the HTML page, use Page.setTitle"); } /** - * Gets currently set URI fragment. - * <p> - * To listen changes in fragment, hook a {@link FragmentChangedListener}. + * Shows a notification message on the middle of the root. The message + * automatically disappears ("humanized message"). * - * @return the current fragment in browser uri or null if not known - */ - public String getFragment() { - return fragment; - } - - /** - * Adds a new {@link BrowserWindowResizeListener} to this root. The listener - * will be notified whenever the browser window within which this root - * resides is resized. + * Care should be taken to to avoid XSS vulnerabilities as the caption is + * rendered as html. + * + * @see #showNotification(Notification) + * @see Notification * - * @param resizeListener - * the listener to add + * @param caption + * The message * - * @see BrowserWindowResizeListener#browserWindowResized(BrowserWindowResizeEvent) - * @see #setResizeLazy(boolean) + * @deprecated As of 7.0, use Notification.show instead */ - public void addListener(BrowserWindowResizeListener resizeListener) { - addListener(BrowserWindowResizeEvent.class, resizeListener, - BROWSWER_RESIZE_METHOD); + @Deprecated + public void showNotification(String caption) { + getPage().showNotification(new Notification(caption)); } /** - * Removes a {@link BrowserWindowResizeListener} from this root. The - * listener will no longer be notified when the browser window is resized. + * Shows a notification message the root. The position and behavior of the + * message depends on the type, which is one of the basic types defined in + * {@link Notification}, for instance Notification.TYPE_WARNING_MESSAGE. + * + * Care should be taken to to avoid XSS vulnerabilities as the caption is + * rendered as html. * - * @param resizeListener - * the listener to remove + * @see #showNotification(Notification) + * @see Notification + * + * @param caption + * The message + * @param type + * The message type + * + * @deprecated As of 7.0, use Notification.show instead */ - public void removeListener(BrowserWindowResizeListener resizeListener) { - removeListener(BrowserWindowResizeEvent.class, resizeListener, - BROWSWER_RESIZE_METHOD); + @Deprecated + public void showNotification(String caption, int type) { + getPage().showNotification(new Notification(caption, type)); } /** - * Gets the last known height of the browser window in which this root - * resides. + * Shows a notification consisting of a bigger caption and a smaller + * description on the middle of the root. The message automatically + * disappears ("humanized message"). + * + * Care should be taken to to avoid XSS vulnerabilities as the caption and + * description are rendered as html. * - * @return the browser window height in pixels + * @see #showNotification(Notification) + * @see Notification + * + * @param caption + * The caption of the message + * @param description + * The message description + * + * @deprecated As of 7.0, use Notification.show instead */ - public int getBrowserWindowHeight() { - return browserWindowHeight; + @Deprecated + public void showNotification(String caption, String description) { + getPage().showNotification(new Notification(caption, description)); } /** - * Gets the last known width of the browser window in which this root - * resides. + * Shows a notification consisting of a bigger caption and a smaller + * description. The position and behavior of the message depends on the + * type, which is one of the basic types defined in {@link Notification} , + * for instance Notification.TYPE_WARNING_MESSAGE. + * + * Care should be taken to to avoid XSS vulnerabilities as the caption and + * description are rendered as html. * - * @return the browser window width in pixels + * @see #showNotification(Notification) + * @see Notification + * + * @param caption + * The caption of the message + * @param description + * The message description + * @param type + * The message type + * + * @deprecated As of 7.0, use Notification.show instead */ - public int getBrowserWindowWidth() { - return browserWindowWidth; + @Deprecated + public void showNotification(String caption, String description, int type) { + getPage() + .showNotification(new Notification(caption, description, type)); } /** - * Notifies the child components and windows that the root is attached to - * the application. + * Shows a notification consisting of a bigger caption and a smaller + * description. The position and behavior of the message depends on the + * type, which is one of the basic types defined in {@link Notification} , + * for instance Notification.TYPE_WARNING_MESSAGE. + * + * Care should be taken to avoid XSS vulnerabilities if html content is + * allowed. + * + * @see #showNotification(Notification) + * @see Notification + * + * @param caption + * The message caption + * @param description + * The message description + * @param type + * The type of message + * @param htmlContentAllowed + * Whether html in the caption and description should be + * displayed as html or as plain text + * + * @deprecated As of 7.0, use Notification.show instead */ - @Override - public void attach() { - super.attach(); - for (Window w : windows) { - w.attach(); - } + @Deprecated + public void showNotification(String caption, String description, int type, + boolean htmlContentAllowed) { + getPage() + .showNotification( + new Notification(caption, description, type, + htmlContentAllowed)); } /** - * Notifies the child components and windows that the root is detached from - * the application. + * Shows a notification message. + * + * @see Notification + * @see #showNotification(String) + * @see #showNotification(String, int) + * @see #showNotification(String, String) + * @see #showNotification(String, String, int) + * + * @param notification + * The notification message to show + * + * @deprecated As of 7.0, use Notification.show instead */ - @Override - public void detach() { - super.detach(); - for (Window w : windows) { - w.detach(); - } - } - - @Override - public boolean isConnectorEnabled() { - // TODO How can a Root be invisible? What does it mean? - return isVisible() && isEnabled(); - } - - public DirtyConnectorTracker getDirtyConnectorTracker() { - return dirtyConnectorTracker; - } - - public void componentAttached(Component component) { - getDirtyConnectorTracker().componentAttached(component); - } - - public void componentDetached(Component component) { - getDirtyConnectorTracker().componentDetached(component); + @Deprecated + public void showNotification(Notification notification) { + getPage().showNotification(notification); } } diff --git a/src/com/vaadin/ui/TabSheet.java b/src/com/vaadin/ui/TabSheet.java index 23dee15359..061809de67 100644 --- a/src/com/vaadin/ui/TabSheet.java +++ b/src/com/vaadin/ui/TabSheet.java @@ -108,6 +108,7 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, setWidth(100, UNITS_PERCENTAGE); setImmediate(true); setCloseHandler(new CloseHandler() { + public void onTabClose(TabSheet tabsheet, Component c) { tabsheet.removeComponent(c); } @@ -120,6 +121,7 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, * * @return the unmodifiable Iterator of the tab content components */ + public Iterator<Component> getComponentIterator() { return Collections.unmodifiableList(components).iterator(); } @@ -130,6 +132,7 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, * * @return the number of contained components */ + public int getComponentCount() { return components.size(); } @@ -143,6 +146,7 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, * @param c * the component to be removed. */ + @Override public void removeComponent(Component c) { if (c != null && components.contains(c)) { @@ -193,6 +197,7 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, * @param c * the component to be added. */ + @Override public void addComponent(Component c) { addTab(c); @@ -334,6 +339,7 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, * @param source * the container components are removed from. */ + @Override public void moveComponentsFrom(ComponentContainer source) { for (final Iterator<Component> i = source.getComponentIterator(); i @@ -359,6 +365,7 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, * @throws PaintException * if the paint operation failed. */ + public void paintContent(PaintTarget target) throws PaintException { if (areTabsHidden()) { @@ -683,6 +690,7 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, } // inherits javadoc + public void changeVariables(Object source, Map<String, Object> variables) { if (variables.containsKey("selected")) { setSelectedTab(keyMapper.get((String) variables.get("selected"))); @@ -719,6 +727,7 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, * * {@inheritDoc} */ + public void replaceComponent(Component oldComponent, Component newComponent) { if (selected == oldComponent) { @@ -729,25 +738,6 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, Tab newTab = tabs.get(newComponent); Tab oldTab = tabs.get(oldComponent); - // Gets the captions - String oldCaption = null; - Resource oldIcon = null; - String newCaption = null; - Resource newIcon = null; - - if (oldTab != null) { - oldCaption = oldTab.getCaption(); - oldIcon = oldTab.getIcon(); - } - - if (newTab != null) { - newCaption = newTab.getCaption(); - newIcon = newTab.getIcon(); - } else { - newCaption = newComponent.getCaption(); - newIcon = newComponent.getIcon(); - } - // Gets the locations int oldLocation = -1; int newLocation = -1; @@ -769,35 +759,21 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, addComponent(newComponent); } else if (newLocation == -1) { removeComponent(oldComponent); - keyMapper.remove(oldComponent); - newTab = addTab(newComponent); - components.remove(newComponent); - components.add(oldLocation, newComponent); - newTab.setCaption(oldCaption); - newTab.setIcon(oldIcon); + newTab = addTab(newComponent, oldLocation); + // Copy all relevant metadata to the new tab (#8793) + // TODO Should reuse the old tab instance instead? + copyTabMetadata(oldTab, newTab); } else { - if (oldLocation > newLocation) { - components.remove(oldComponent); - components.add(newLocation, oldComponent); - components.remove(newComponent); - components.add(oldLocation, newComponent); - } else { - components.remove(newComponent); - components.add(oldLocation, newComponent); - components.remove(oldComponent); - components.add(newLocation, oldComponent); - } + components.set(oldLocation, newComponent); + components.set(newLocation, oldComponent); - if (newTab != null) { - // This should always be true - newTab.setCaption(oldCaption); - newTab.setIcon(oldIcon); - } - if (oldTab != null) { - // This should always be true - oldTab.setCaption(newCaption); - oldTab.setIcon(newIcon); - } + // Tab associations are not changed, but metadata is swapped between + // the instances + // TODO Should reassociate the instances instead? + Tab tmp = new TabSheetTabImpl(null, null); + copyTabMetadata(newTab, tmp); + copyTabMetadata(oldTab, newTab); + copyTabMetadata(tmp, oldTab); requestRepaint(); } @@ -1106,6 +1082,7 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, /** * Returns the tab caption. Can never be null. */ + public String getCaption() { return caption; } @@ -1300,4 +1277,23 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, public boolean isComponentVisible(Component childComponent) { return childComponent == getSelectedTab(); } + + /** + * Copies properties from one Tab to another. + * + * @param from + * The tab whose data to copy. + * @param to + * The tab to which copy the data. + */ + private static void copyTabMetadata(Tab from, Tab to) { + to.setCaption(from.getCaption()); + to.setIcon(from.getIcon()); + to.setDescription(from.getDescription()); + to.setVisible(from.isVisible()); + to.setEnabled(from.isEnabled()); + to.setClosable(from.isClosable()); + to.setStyleName(from.getStyleName()); + to.setComponentError(from.getComponentError()); + } } diff --git a/src/com/vaadin/ui/Table.java b/src/com/vaadin/ui/Table.java index ba4c6529c5..a1cc4f95fe 100644 --- a/src/com/vaadin/ui/Table.java +++ b/src/com/vaadin/ui/Table.java @@ -21,13 +21,13 @@ import java.util.StringTokenizer; import java.util.logging.Level; import java.util.logging.Logger; -import com.vaadin.Application; import com.vaadin.data.Container; import com.vaadin.data.Item; import com.vaadin.data.Property; import com.vaadin.data.util.ContainerOrderedWrapper; import com.vaadin.data.util.IndexedContainer; import com.vaadin.data.util.converter.Converter; +import com.vaadin.data.util.converter.ConverterUtil; import com.vaadin.event.Action; import com.vaadin.event.Action.Handler; import com.vaadin.event.DataBoundTransferable; @@ -39,7 +39,6 @@ import com.vaadin.event.dd.DragAndDropEvent; import com.vaadin.event.dd.DragSource; import com.vaadin.event.dd.DropHandler; import com.vaadin.event.dd.DropTarget; -import com.vaadin.event.dd.acceptcriteria.ClientCriterion; import com.vaadin.event.dd.acceptcriteria.ServerSideCriterion; import com.vaadin.terminal.KeyMapper; import com.vaadin.terminal.LegacyPaint; @@ -47,7 +46,6 @@ import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; import com.vaadin.terminal.Resource; import com.vaadin.terminal.gwt.client.MouseEventDetails; -import com.vaadin.terminal.gwt.client.ui.dd.VLazyInitItemIdentifiers; import com.vaadin.terminal.gwt.client.ui.table.VScrollTable; /** @@ -78,8 +76,7 @@ public class Table extends AbstractSelect implements Action.Container, Container.Ordered, Container.Sortable, ItemClickNotifier, DragSource, DropTarget, HasComponents { - private static final Logger logger = Logger - .getLogger(Table.class.getName()); + private transient Logger logger = null; /** * Modes that Table support as drag sourse. @@ -331,7 +328,8 @@ public class Table extends AbstractSelect implements Action.Container, private static final double CACHE_RATE_DEFAULT = 2; private static final String ROW_HEADER_COLUMN_KEY = "0"; - private static final Object ROW_HEADER_FAKE_PROPERTY_ID = new Object(); + private static final Object ROW_HEADER_FAKE_PROPERTY_ID = new UniqueSerializable() { + }; /* Private table extensions to Select */ @@ -356,6 +354,11 @@ public class Table extends AbstractSelect implements Action.Container, private LinkedList<Object> visibleColumns = new LinkedList<Object>(); /** + * Holds noncollapsible columns. + */ + private HashSet<Object> noncollapsibleColumns = new HashSet<Object>(); + + /** * Holds propertyIds of currently collapsed columns. */ private final HashSet<Object> collapsedColumns = new HashSet<Object>(); @@ -473,10 +476,9 @@ public class Table extends AbstractSelect implements Action.Container, private Object sortContainerPropertyId = null; /** - * Is table sorting disabled alltogether; even if some of the properties - * would be sortable. + * Is table sorting by the user enabled. */ - private boolean sortDisabled = false; + private boolean sortEnabled = true; /** * Number of rows explicitly requested by the client to be painted on next @@ -1248,6 +1250,9 @@ public class Table extends AbstractSelect implements Action.Container, if (!isColumnCollapsingAllowed()) { throw new IllegalStateException("Column collapsing not allowed!"); } + if (collapsed && noncollapsibleColumns.contains(propertyId)) { + throw new IllegalStateException("The column is noncollapsible!"); + } if (collapsed) { collapsedColumns.add(propertyId); @@ -1287,6 +1292,41 @@ public class Table extends AbstractSelect implements Action.Container, } /** + * Sets whether the given column is collapsible. Note that collapsible + * columns can only be actually collapsed (via UI or with + * {@link #setColumnCollapsed(Object, boolean) setColumnCollapsed()}) if + * {@link #isColumnCollapsingAllowed()} is true. By default all columns are + * collapsible. + * + * @param propertyId + * the propertyID identifying the column. + * @param collapsible + * true if the column should be collapsible, false otherwise. + */ + public void setColumnCollapsible(Object propertyId, boolean collapsible) { + if (collapsible) { + noncollapsibleColumns.remove(propertyId); + } else { + noncollapsibleColumns.add(propertyId); + collapsedColumns.remove(propertyId); + } + refreshRowCache(); + } + + /** + * Checks if the given column is collapsible. Note that even if this method + * returns <code>true</code>, the column can only be actually collapsed (via + * UI or with {@link #setColumnCollapsed(Object, boolean) + * setColumnCollapsed()}) if {@link #isColumnCollapsingAllowed()} is also + * true. + * + * @return true if the column can be collapsed; false otherwise. + */ + public boolean isColumnCollapsible(Object propertyId) { + return !noncollapsibleColumns.contains(propertyId); + } + + /** * Checks if column reordering is allowed. * * @return true if columns can be reordered; false otherwise. @@ -1569,6 +1609,13 @@ public class Table extends AbstractSelect implements Action.Container, } } else { // initial load + + // #8805 send one extra row in the beginning in case a partial + // row is shown on the UI + if (firstIndex > 0) { + firstIndex = firstIndex - 1; + rows = rows + 1; + } firstToBeRenderedInClient = firstIndex; } if (totalRows > 0) { @@ -1597,12 +1644,21 @@ public class Table extends AbstractSelect implements Action.Container, * this method has been called. See {@link #refreshRowCache()} for forcing * an update of the contents. */ + @Override public void requestRepaint() { // Overridden only for javadoc super.requestRepaint(); } + @Override + public void requestRepaintAll() { + super.requestRepaintAll(); + + // Avoid sending a partial repaint (#8714) + refreshRowCache(); + } + private void removeRowsFromCacheAndFillBottom(int firstIndex, int rows) { int totalCachedRows = pageBuffer[CELL_ITEMID].length; int totalRows = size(); @@ -1706,8 +1762,9 @@ public class Table extends AbstractSelect implements Action.Container, * @return */ private Object[][] getVisibleCellsInsertIntoCache(int firstIndex, int rows) { - logger.finest("Insert " + rows + " rows at index " + firstIndex - + " to existing page buffer requested"); + getLogger().finest( + "Insert " + rows + " rows at index " + firstIndex + + " to existing page buffer requested"); // Page buffer must not become larger than pageLength*cacheRate before // or after the current page @@ -1810,11 +1867,14 @@ public class Table extends AbstractSelect implements Action.Container, } } pageBuffer = newPageBuffer; - logger.finest("Page Buffer now contains " - + pageBuffer[CELL_ITEMID].length + " rows (" - + pageBufferFirstIndex + "-" - + (pageBufferFirstIndex + pageBuffer[CELL_ITEMID].length - 1) - + ")"); + getLogger().finest( + "Page Buffer now contains " + + pageBuffer[CELL_ITEMID].length + + " rows (" + + pageBufferFirstIndex + + "-" + + (pageBufferFirstIndex + + pageBuffer[CELL_ITEMID].length - 1) + ")"); return cells; } @@ -1831,8 +1891,9 @@ public class Table extends AbstractSelect implements Action.Container, */ private Object[][] getVisibleCellsNoCache(int firstIndex, int rows, boolean replaceListeners) { - logger.finest("Render visible cells for rows " + firstIndex + "-" - + (firstIndex + rows - 1)); + getLogger().finest( + "Render visible cells for rows " + firstIndex + "-" + + (firstIndex + rows - 1)); final Object[] colids = getVisibleColumns(); final int cols = colids.length; @@ -2014,8 +2075,9 @@ public class Table extends AbstractSelect implements Action.Container, } protected void registerComponent(Component component) { - logger.finest("Registered " + component.getClass().getSimpleName() - + ": " + component.getCaption()); + getLogger().finest( + "Registered " + component.getClass().getSimpleName() + ": " + + component.getCaption()); if (component.getParent() != this) { component.setParent(this); } @@ -2046,8 +2108,9 @@ public class Table extends AbstractSelect implements Action.Container, * @param count */ private void unregisterComponentsAndPropertiesInRows(int firstIx, int count) { - logger.finest("Unregistering components in rows " + firstIx + "-" - + (firstIx + count - 1)); + getLogger().finest( + "Unregistering components in rows " + firstIx + "-" + + (firstIx + count - 1)); Object[] colids = getVisibleColumns(); if (pageBuffer != null && pageBuffer[CELL_ITEMID].length > 0) { int bufSize = pageBuffer[CELL_ITEMID].length; @@ -2127,8 +2190,9 @@ public class Table extends AbstractSelect implements Action.Container, * a set of components that should be unregistered. */ protected void unregisterComponent(Component component) { - logger.finest("Unregistered " + component.getClass().getSimpleName() - + ": " + component.getCaption()); + getLogger().finest( + "Unregistered " + component.getClass().getSimpleName() + ": " + + component.getCaption()); component.setParent(null); /* * Also remove property data sources to unregister listeners keeping the @@ -2438,6 +2502,7 @@ public class Table extends AbstractSelect implements Action.Container, * @see com.vaadin.ui.Select#changeVariables(java.lang.Object, * java.util.Map) */ + @Override public void changeVariables(Object source, Map<String, Object> variables) { @@ -2497,7 +2562,7 @@ public class Table extends AbstractSelect implements Action.Container, .get("lastToBeRendered")).intValue(); } catch (Exception e) { // FIXME: Handle exception - logger.log(Level.FINER, + getLogger().log(Level.FINER, "Could not parse the first and/or last rows.", e); } @@ -2517,12 +2582,13 @@ public class Table extends AbstractSelect implements Action.Container, } } } - logger.finest("Client wants rows " + reqFirstRowToPaint + "-" - + (reqFirstRowToPaint + reqRowsToPaint - 1)); + getLogger().finest( + "Client wants rows " + reqFirstRowToPaint + "-" + + (reqFirstRowToPaint + reqRowsToPaint - 1)); clientNeedsContentRefresh = true; } - if (!sortDisabled) { + if (isSortEnabled()) { // Sorting boolean doSort = false; if (variables.containsKey("sortcolumn")) { @@ -2564,7 +2630,7 @@ public class Table extends AbstractSelect implements Action.Container, } } catch (final Exception e) { // FIXME: Handle exception - logger.log(Level.FINER, + getLogger().log(Level.FINER, "Could not determine column collapsing state", e); } clientNeedsContentRefresh = true; @@ -2586,7 +2652,7 @@ public class Table extends AbstractSelect implements Action.Container, } } catch (final Exception e) { // FIXME: Handle exception - logger.log(Level.FINER, + getLogger().log(Level.FINER, "Could not determine column reordering state", e); } clientNeedsContentRefresh = true; @@ -2759,6 +2825,7 @@ public class Table extends AbstractSelect implements Action.Container, * @see com.vaadin.ui.AbstractSelect#paintContent(com.vaadin. * terminal.PaintTarget) */ + @Override public void paintContent(PaintTarget target) throws PaintException { /* @@ -2876,8 +2943,9 @@ public class Table extends AbstractSelect implements Action.Container, target.startTag("prows"); if (!shouldHideAddedRows()) { - logger.finest("Paint rows for add. Index: " + firstIx + ", count: " - + count + "."); + getLogger().finest( + "Paint rows for add. Index: " + firstIx + ", count: " + + count + "."); // Partial row additions bypass the normal caching mechanism. Object[][] cells = getVisibleCellsInsertIntoCache(firstIx, count); @@ -2900,8 +2968,9 @@ public class Table extends AbstractSelect implements Action.Container, indexInRowbuffer, itemId); } } else { - logger.finest("Paint rows for remove. Index: " + firstIx - + ", count: " + count + "."); + getLogger().finest( + "Paint rows for remove. Index: " + firstIx + ", count: " + + count + "."); removeRowsFromCacheAndFillBottom(firstIx, count); target.addAttribute("hide", true); } @@ -3100,7 +3169,17 @@ public class Table extends AbstractSelect implements Action.Container, } } target.addVariable(this, "collapsedcolumns", collapsedKeys); + + final String[] noncollapsibleKeys = new String[noncollapsibleColumns + .size()]; + nextColumn = 0; + for (Object colId : noncollapsibleColumns) { + noncollapsibleKeys[nextColumn++] = columnIdMap.key(colId); + } + target.addVariable(this, "noncollapsiblecolumns", + noncollapsibleKeys); } + } private void paintActions(PaintTarget target, final Set<Action> actionSet) @@ -3584,12 +3663,8 @@ public class Table extends AbstractSelect implements Action.Container, if (hasConverter(colId)) { converter = getConverter(colId); } else { - Application app = Application.getCurrentApplication(); - if (app != null) { - converter = (Converter<String, Object>) app - .getConverterFactory().createConverter(String.class, - property.getType()); - } + ConverterUtil.getConverter(String.class, property.getType(), + getApplication()); } Object value = property.getValue(); if (converter != null) { @@ -3605,6 +3680,7 @@ public class Table extends AbstractSelect implements Action.Container, * * @see com.vaadin.event.Action.Container#addActionHandler(Action.Handler) */ + public void addActionHandler(Action.Handler actionHandler) { if (actionHandler != null) { @@ -3631,6 +3707,7 @@ public class Table extends AbstractSelect implements Action.Container, * * @see com.vaadin.event.Action.Container#removeActionHandler(Action.Handler) */ + public void removeActionHandler(Action.Handler actionHandler) { if (actionHandlers != null && actionHandlers.contains(actionHandler)) { @@ -3670,6 +3747,7 @@ public class Table extends AbstractSelect implements Action.Container, * * @see com.vaadin.data.Property.ValueChangeListener#valueChange(Property.ValueChangeEvent) */ + @Override public void valueChange(Property.ValueChangeEvent event) { if (event.getProperty() == this @@ -3700,18 +3778,12 @@ public class Table extends AbstractSelect implements Action.Container, * * @see com.vaadin.ui.Component#attach() */ + @Override public void attach() { super.attach(); refreshRenderedCells(); - - if (visibleComponents != null) { - for (final Iterator<Component> i = visibleComponents.iterator(); i - .hasNext();) { - i.next().attach(); - } - } } /** @@ -3719,16 +3791,10 @@ public class Table extends AbstractSelect implements Action.Container, * * @see com.vaadin.ui.Component#detach() */ + @Override public void detach() { super.detach(); - - if (visibleComponents != null) { - for (final Iterator<Component> i = visibleComponents.iterator(); i - .hasNext();) { - i.next().detach(); - } - } } /** @@ -3736,6 +3802,7 @@ public class Table extends AbstractSelect implements Action.Container, * * @see com.vaadin.data.Container#removeAllItems() */ + @Override public boolean removeAllItems() { currentPageFirstItemId = null; @@ -3748,6 +3815,7 @@ public class Table extends AbstractSelect implements Action.Container, * * @see com.vaadin.data.Container#removeItem(Object) */ + @Override public boolean removeItem(Object itemId) { final Object nextItemId = nextItemId(itemId); @@ -3766,6 +3834,7 @@ public class Table extends AbstractSelect implements Action.Container, * * @see com.vaadin.data.Container#removeContainerProperty(Object) */ + @Override public boolean removeContainerProperty(Object propertyId) throws UnsupportedOperationException { @@ -3792,6 +3861,7 @@ public class Table extends AbstractSelect implements Action.Container, * @see com.vaadin.data.Container#addContainerProperty(Object, Class, * Object) */ + @Override public boolean addContainerProperty(Object propertyId, Class<?> type, Object defaultValue) throws UnsupportedOperationException { @@ -3947,6 +4017,7 @@ public class Table extends AbstractSelect implements Action.Container, * * @see com.vaadin.ui.Select#getVisibleItemIds() */ + @Override public Collection<?> getVisibleItemIds() { @@ -3970,6 +4041,7 @@ public class Table extends AbstractSelect implements Action.Container, * * @see com.vaadin.data.Container.ItemSetChangeListener#containerItemSetChange(com.vaadin.data.Container.ItemSetChangeEvent) */ + @Override public void containerItemSetChange(Container.ItemSetChangeEvent event) { super.containerItemSetChange(event); @@ -3986,6 +4058,7 @@ public class Table extends AbstractSelect implements Action.Container, * * @see com.vaadin.data.Container.PropertySetChangeListener#containerPropertySetChange(com.vaadin.data.Container.PropertySetChangeEvent) */ + @Override public void containerPropertySetChange( Container.PropertySetChangeEvent event) { @@ -4029,6 +4102,7 @@ public class Table extends AbstractSelect implements Action.Container, * if set to true. * @see com.vaadin.ui.Select#setNewItemsAllowed(boolean) */ + @Override public void setNewItemsAllowed(boolean allowNewOptions) throws UnsupportedOperationException { @@ -4042,6 +4116,7 @@ public class Table extends AbstractSelect implements Action.Container, * * @see com.vaadin.data.Container.Ordered#nextItemId(java.lang.Object) */ + public Object nextItemId(Object itemId) { return ((Container.Ordered) items).nextItemId(itemId); } @@ -4052,6 +4127,7 @@ public class Table extends AbstractSelect implements Action.Container, * * @see com.vaadin.data.Container.Ordered#prevItemId(java.lang.Object) */ + public Object prevItemId(Object itemId) { return ((Container.Ordered) items).prevItemId(itemId); } @@ -4061,6 +4137,7 @@ public class Table extends AbstractSelect implements Action.Container, * * @see com.vaadin.data.Container.Ordered#firstItemId() */ + public Object firstItemId() { return ((Container.Ordered) items).firstItemId(); } @@ -4070,6 +4147,7 @@ public class Table extends AbstractSelect implements Action.Container, * * @see com.vaadin.data.Container.Ordered#lastItemId() */ + public Object lastItemId() { return ((Container.Ordered) items).lastItemId(); } @@ -4080,6 +4158,7 @@ public class Table extends AbstractSelect implements Action.Container, * * @see com.vaadin.data.Container.Ordered#isFirstId(java.lang.Object) */ + public boolean isFirstId(Object itemId) { return ((Container.Ordered) items).isFirstId(itemId); } @@ -4090,6 +4169,7 @@ public class Table extends AbstractSelect implements Action.Container, * * @see com.vaadin.data.Container.Ordered#isLastId(java.lang.Object) */ + public boolean isLastId(Object itemId) { return ((Container.Ordered) items).isLastId(itemId); } @@ -4099,6 +4179,7 @@ public class Table extends AbstractSelect implements Action.Container, * * @see com.vaadin.data.Container.Ordered#addItemAfter(java.lang.Object) */ + public Object addItemAfter(Object previousItemId) throws UnsupportedOperationException { Object itemId = ((Container.Ordered) items) @@ -4115,6 +4196,7 @@ public class Table extends AbstractSelect implements Action.Container, * @see com.vaadin.data.Container.Ordered#addItemAfter(java.lang.Object, * java.lang.Object) */ + public Item addItemAfter(Object previousItemId, Object newItemId) throws UnsupportedOperationException { Item item = ((Container.Ordered) items).addItemAfter(previousItemId, @@ -4207,6 +4289,7 @@ public class Table extends AbstractSelect implements Action.Container, * boolean[]) * */ + public void sort(Object[] propertyId, boolean[] ascending) throws UnsupportedOperationException { final Container c = getContainerDataSource(); @@ -4239,15 +4322,21 @@ public class Table extends AbstractSelect implements Action.Container, /** * Gets the container property IDs, which can be used to sort the item. + * <p> + * Note that the {@link #isSortEnabled()} state affects what this method + * returns. Disabling sorting causes this method to always return an empty + * collection. + * </p> * * @see com.vaadin.data.Container.Sortable#getSortableContainerPropertyIds() */ + public Collection<?> getSortableContainerPropertyIds() { final Container c = getContainerDataSource(); - if (c instanceof Container.Sortable && !isSortDisabled()) { + if (c instanceof Container.Sortable && isSortEnabled()) { return ((Container.Sortable) c).getSortableContainerPropertyIds(); } else { - return new LinkedList<Object>(); + return Collections.EMPTY_LIST; } } @@ -4338,37 +4427,48 @@ public class Table extends AbstractSelect implements Action.Container, * would support this. * * @return True iff sorting is disabled. + * @deprecated Use {@link #isSortEnabled()} instead */ + @Deprecated public boolean isSortDisabled() { - return sortDisabled; + return !isSortEnabled(); } /** - * Disables the sorting altogether. + * Checks if sorting is enabled. * - * To disable sorting altogether, set to true. In this case no sortable - * columns are given even in the case where datasource would support this. + * @return true if sorting by the user is allowed, false otherwise + */ + public boolean isSortEnabled() { + return sortEnabled; + } + + /** + * Disables the sorting by the user altogether. * * @param sortDisabled * True iff sorting is disabled. + * @deprecated Use {@link #setSortEnabled(boolean)} instead */ + @Deprecated public void setSortDisabled(boolean sortDisabled) { - if (this.sortDisabled != sortDisabled) { - this.sortDisabled = sortDisabled; - requestRepaint(); - } + setSortEnabled(!sortDisabled); } /** - * Table does not support lazy options loading mode. Setting this true will - * throw UnsupportedOperationException. + * Enables or disables sorting. + * <p> + * Setting this to false disallows sorting by the user. It is still possible + * to call {@link #sort()}. + * </p> * - * @see com.vaadin.ui.Select#setLazyLoading(boolean) + * @param sortEnabled + * true to allow the user to sort the table, false to disallow it */ - public void setLazyLoading(boolean useLazyLoading) { - if (useLazyLoading) { - throw new UnsupportedOperationException( - "Lazy options loading is not supported by Table."); + public void setSortEnabled(boolean sortEnabled) { + if (this.sortEnabled != sortEnabled) { + this.sortEnabled = sortEnabled; + requestRepaint(); } } @@ -4455,6 +4555,7 @@ public class Table extends AbstractSelect implements Action.Container, } // Identical to AbstractCompoenentContainer.setEnabled(); + @Override public void setEnabled(boolean enabled) { super.setEnabled(enabled); @@ -4466,10 +4567,6 @@ public class Table extends AbstractSelect implements Action.Container, } } - public void requestRepaintAll() { - AbstractComponentContainer.requestRepaintAll(this); - } - /** * Sets the drag start mode of the Table. Drag start mode controls how Table * behaves as a drag source. @@ -4583,7 +4680,6 @@ public class Table extends AbstractSelect implements Action.Container, * initialized from server and no subsequent requests requests are needed * during that drag and drop operation. */ - @ClientCriterion(VLazyInitItemIdentifiers.class) public static abstract class TableDropCriterion extends ServerSideCriterion { private Table table; @@ -4597,6 +4693,7 @@ public class Table extends AbstractSelect implements Action.Container, * com.vaadin.event.dd.acceptcriteria.ServerSideCriterion#getIdentifier * () */ + @Override protected String getIdentifier() { return TableDropCriterion.class.getCanonicalName(); @@ -4628,6 +4725,7 @@ public class Table extends AbstractSelect implements Action.Container, * com.vaadin.event.dd.acceptcriteria.AcceptCriterion#paintResponse( * com.vaadin.terminal.PaintTarget) */ + @Override public void paintResponse(PaintTarget target) throws PaintException { /* @@ -5299,4 +5397,11 @@ public class Table extends AbstractSelect implements Action.Container, public boolean isComponentVisible(Component childComponent) { return true; } + + private final Logger getLogger() { + if (logger == null) { + logger = Logger.getLogger(Table.class.getName()); + } + return logger; + } } diff --git a/src/com/vaadin/ui/Tree.java b/src/com/vaadin/ui/Tree.java index db738fee58..dacb3a2027 100644 --- a/src/com/vaadin/ui/Tree.java +++ b/src/com/vaadin/ui/Tree.java @@ -34,7 +34,6 @@ import com.vaadin.event.dd.DragSource; import com.vaadin.event.dd.DropHandler; import com.vaadin.event.dd.DropTarget; import com.vaadin.event.dd.TargetDetails; -import com.vaadin.event.dd.acceptcriteria.ClientCriterion; import com.vaadin.event.dd.acceptcriteria.ClientSideCriterion; import com.vaadin.event.dd.acceptcriteria.ServerSideCriterion; import com.vaadin.event.dd.acceptcriteria.TargetDetailIs; @@ -43,8 +42,6 @@ import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; import com.vaadin.terminal.Resource; import com.vaadin.terminal.gwt.client.MouseEventDetails; -import com.vaadin.terminal.gwt.client.ui.dd.VLazyInitItemIdentifiers; -import com.vaadin.terminal.gwt.client.ui.dd.VTargetInSubtree; import com.vaadin.terminal.gwt.client.ui.dd.VerticalDropLocation; import com.vaadin.terminal.gwt.client.ui.tree.TreeConnector; import com.vaadin.terminal.gwt.client.ui.tree.VTree; @@ -1396,7 +1393,6 @@ public class Tree extends AbstractSelect implements Container.Hierarchical, * initialized from server and no subsequent requests requests are needed * during that drag and drop operation. */ - @ClientCriterion(VLazyInitItemIdentifiers.class) public static abstract class TreeDropCriterion extends ServerSideCriterion { private Tree tree; @@ -1513,7 +1509,6 @@ public class Tree extends AbstractSelect implements Container.Hierarchical, * <p> * The root items is also consider to be valid target. */ - @ClientCriterion(VTargetInSubtree.class) public class TargetInSubtree extends ClientSideCriterion { private Object rootId; diff --git a/src/com/vaadin/ui/TreeTable.java b/src/com/vaadin/ui/TreeTable.java index 9607add2c9..3294f6fab0 100644 --- a/src/com/vaadin/ui/TreeTable.java +++ b/src/com/vaadin/ui/TreeTable.java @@ -49,9 +49,6 @@ import com.vaadin.ui.Tree.ExpandListener; @SuppressWarnings({ "serial" }) public class TreeTable extends Table implements Hierarchical { - private static final Logger logger = Logger.getLogger(TreeTable.class - .getName()); - private interface ContainerStrategy extends Serializable { public int size(); @@ -84,6 +81,7 @@ public class TreeTable extends Table implements Hierarchical { * Consider adding getDepth to {@link Collapsible}, might help * scalability with some container implementations. */ + public int getDepth(Object itemId) { int depth = 0; Hierarchical hierarchicalContainer = getContainerDataSource(); @@ -222,9 +220,9 @@ public class TreeTable extends Table implements Hierarchical { boolean removed = openItems.remove(itemId); if (!removed) { openItems.add(itemId); - logger.finest("Item " + itemId + " is now expanded"); + getLogger().finest("Item " + itemId + " is now expanded"); } else { - logger.finest("Item " + itemId + " is now collapsed"); + getLogger().finest("Item " + itemId + " is now collapsed"); } clearPreorderCache(); } @@ -789,4 +787,8 @@ public class TreeTable extends Table implements Hierarchical { requestRepaint(); } + private static final Logger getLogger() { + return Logger.getLogger(TreeTable.class.getName()); + } + } diff --git a/src/com/vaadin/ui/UniqueSerializable.java b/src/com/vaadin/ui/UniqueSerializable.java new file mode 100644 index 0000000000..828b285538 --- /dev/null +++ b/src/com/vaadin/ui/UniqueSerializable.java @@ -0,0 +1,30 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.ui; + +import java.io.Serializable; + +/** + * A base class for generating an unique object that is serializable. + * <p> + * This class is abstract but has no abstract methods to force users to create + * an anonymous inner class. Otherwise each instance will not be unique. + * + * @author Vaadin Ltd + * @version @VERSION@ + * @since 7.0 + * + */ +public abstract class UniqueSerializable implements Serializable { + + @Override + public int hashCode() { + return getClass().hashCode(); + } + + @Override + public boolean equals(Object obj) { + return getClass() == obj.getClass(); + } +} diff --git a/src/com/vaadin/ui/Window.java b/src/com/vaadin/ui/Window.java index 3c17baf414..8866362587 100644 --- a/src/com/vaadin/ui/Window.java +++ b/src/com/vaadin/ui/Window.java @@ -8,7 +8,6 @@ import java.io.Serializable; import java.lang.reflect.Method; import java.util.Map; -import com.vaadin.Application; import com.vaadin.event.FieldEvents.BlurEvent; import com.vaadin.event.FieldEvents.BlurListener; import com.vaadin.event.FieldEvents.BlurNotifier; @@ -24,47 +23,26 @@ import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; import com.vaadin.terminal.Vaadin6Component; import com.vaadin.terminal.gwt.client.MouseEventDetails; +import com.vaadin.terminal.gwt.client.ui.root.VRoot; import com.vaadin.terminal.gwt.client.ui.window.WindowServerRpc; import com.vaadin.terminal.gwt.client.ui.window.WindowState; /** - * A component that represents an application (browser native) window or a sub - * window. + * A component that represents a floating popup window that can be added to a + * {@link Root}. A window is added to a {@code Root} using + * {@link Root#addWindow(Window)}. </p> * <p> - * If the window is a application window or a sub window depends on how it is - * added to the application. Adding a {@code Window} to a {@code Window} using - * {@link Window#addWindow(Window)} makes it a sub window and adding a - * {@code Window} to the {@code Application} using - * {@link Application#addWindow(Window)} makes it an application window. + * The contents of a window is set using {@link #setContent(ComponentContainer)} + * or by using the {@link #Window(String, ComponentContainer)} constructor. The + * contents can in turn contain other components. By default, a + * {@link VerticalLayout} is used as content. * </p> * <p> - * An application window is the base of any view in a Vaadin application. All - * applications contain a main application window (set using - * {@link Application#setMainWindow(Window)} which is what is initially shown to - * the user. The contents of a window is set using - * {@link #setContent(ComponentContainer)}. The contents can in turn contain - * other components. For multi-tab applications there is one window instance per - * opened tab. + * A window can be positioned on the screen using absolute coordinates (pixels) + * or set to be centered using {@link #center()} * </p> * <p> - * A sub window is floating popup style window that can be added to an - * application window. Like the application window its content is set using - * {@link #setContent(ComponentContainer)}. A sub window can be positioned on - * the screen using absolute coordinates (pixels). The default content of the - * Window is set to be suitable for application windows. For sub windows it - * might be necessary to set the size of the content to work as expected. - * </p> - * <p> - * Window caption is displayed in the browser title bar for application level - * windows and in the window header for sub windows. - * </p> - * <p> - * Certain methods in this class are only meaningful for sub windows and other - * parts only for application windows. These are marked using <b>Sub window - * only</b> and <b>Application window only</b> respectively in the javadoc. - * </p> - * <p> - * Sub window is to be split into a separate component in Vaadin 7. + * The caption is displayed in the window header. * </p> * * @author Vaadin Ltd. @@ -83,6 +61,10 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier, } }; + private int browserWindowWidth = -1; + + private int browserWindowHeight = -1; + /** * Creates a new unnamed window with a default layout. */ @@ -119,6 +101,7 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier, * * @see com.vaadin.ui.Panel#addComponent(com.vaadin.ui.Component) */ + @Override public void addComponent(Component c) { if (c instanceof Window) { @@ -136,6 +119,7 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier, * * @see com.vaadin.ui.Panel#paintContent(com.vaadin.terminal.PaintTarget) */ + @Override public synchronized void paintContent(PaintTarget target) throws PaintException { @@ -153,6 +137,7 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier, * * @see com.vaadin.ui.Panel#changeVariables(java.lang.Object, java.util.Map) */ + @Override public void changeVariables(Object source, Map<String, Object> variables) { @@ -161,15 +146,29 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier, // size is handled in super class, but resize events only in windows -> // so detect if size change occurs before super.changeVariables() if (variables.containsKey("height") - && (getHeightUnits() != UNITS_PIXELS || (Integer) variables + && (getHeightUnits() != Unit.PIXELS || (Integer) variables .get("height") != getHeight())) { sizeHasChanged = true; } if (variables.containsKey("width") - && (getWidthUnits() != UNITS_PIXELS || (Integer) variables + && (getWidthUnits() != Unit.PIXELS || (Integer) variables .get("width") != getWidth())) { sizeHasChanged = true; } + Integer browserHeightVar = (Integer) variables + .get(VRoot.BROWSER_HEIGHT_VAR); + if (browserHeightVar != null + && browserHeightVar.intValue() != browserWindowHeight) { + browserWindowHeight = browserHeightVar.intValue(); + sizeHasChanged = true; + } + Integer browserWidthVar = (Integer) variables + .get(VRoot.BROWSER_WIDTH_VAR); + if (browserWidthVar != null + && browserWidthVar.intValue() != browserWindowWidth) { + browserWindowWidth = browserWidthVar.intValue(); + sizeHasChanged = true; + } super.changeVariables(source, variables); @@ -604,8 +603,13 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier, } /** - * Request to center this window on the screen. <b>Note:</b> affects - * sub-windows only. + * Sets this window to be centered relative to its parent window. Affects + * sub-windows only. If the window is resized as a result of the size of its + * content changing, it will keep itself centered as long as its position is + * not explicitly changed programmatically or by the user. + * <p> + * <b>NOTE:</b> This method has several issues as currently implemented. + * Please refer to http://dev.vaadin.com/ticket/8971 for details. */ public void center() { getState().setCentered(true); @@ -788,6 +792,7 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier, * * @see com.vaadin.event.FieldEvents.FocusNotifier#addListener(com.vaadin.event.FieldEvents.FocusListener) */ + public void addListener(FocusListener listener) { addListener(FocusEvent.EVENT_ID, FocusEvent.class, listener, FocusListener.focusMethod); @@ -804,6 +809,7 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier, * * @see com.vaadin.event.FieldEvents.BlurNotifier#addListener(com.vaadin.event.FieldEvents.BlurListener) */ + public void addListener(BlurListener listener) { addListener(BlurEvent.EVENT_ID, BlurEvent.class, listener, BlurListener.blurMethod); @@ -819,6 +825,7 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier, * If the window is a sub-window focusing will cause the sub-window to be * brought on top of other sub-windows on gain keyboard focus. */ + @Override public void focus() { /* @@ -834,5 +841,4 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier, public WindowState getState() { return (WindowState) super.getState(); } - } diff --git a/src/com/vaadin/ui/themes/ChameleonTheme.java b/src/com/vaadin/ui/themes/ChameleonTheme.java index bfb9686018..5ae8cd4e57 100644 --- a/src/com/vaadin/ui/themes/ChameleonTheme.java +++ b/src/com/vaadin/ui/themes/ChameleonTheme.java @@ -5,7 +5,7 @@ package com.vaadin.ui.themes; public class ChameleonTheme extends BaseTheme { - public static final String THEME_NAME = "Chameleon"; + public static final String THEME_NAME = "chameleon"; /*************************************************************************** * Label styles |