You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

AbstractSingleComponentContainer.java 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. /*
  2. * Copyright 2000-2014 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.ui;
  17. import java.util.Collections;
  18. import java.util.Iterator;
  19. import org.jsoup.nodes.Element;
  20. import com.vaadin.server.ComponentSizeValidator;
  21. import com.vaadin.server.VaadinService;
  22. import com.vaadin.server.VaadinSession;
  23. import com.vaadin.ui.declarative.DesignContext;
  24. import com.vaadin.ui.declarative.DesignException;
  25. /**
  26. * Abstract base class for component containers that have only one child
  27. * component.
  28. *
  29. * For component containers that support multiple children, inherit
  30. * {@link AbstractComponentContainer} instead of this class.
  31. *
  32. * @since 7.0
  33. */
  34. public abstract class AbstractSingleComponentContainer extends
  35. AbstractComponent implements SingleComponentContainer {
  36. private Component content;
  37. @Override
  38. public int getComponentCount() {
  39. return (content != null) ? 1 : 0;
  40. }
  41. @Override
  42. public Iterator<Component> iterator() {
  43. if (content != null) {
  44. return Collections.singletonList(content).iterator();
  45. } else {
  46. return Collections.<Component> emptyList().iterator();
  47. }
  48. }
  49. /* documented in interface */
  50. @Override
  51. public void addComponentAttachListener(ComponentAttachListener listener) {
  52. addListener(ComponentAttachEvent.class, listener,
  53. ComponentAttachListener.attachMethod);
  54. }
  55. /* documented in interface */
  56. @Override
  57. public void removeComponentAttachListener(ComponentAttachListener listener) {
  58. removeListener(ComponentAttachEvent.class, listener,
  59. ComponentAttachListener.attachMethod);
  60. }
  61. /* documented in interface */
  62. @Override
  63. public void addComponentDetachListener(ComponentDetachListener listener) {
  64. addListener(ComponentDetachEvent.class, listener,
  65. ComponentDetachListener.detachMethod);
  66. }
  67. /* documented in interface */
  68. @Override
  69. public void removeComponentDetachListener(ComponentDetachListener listener) {
  70. removeListener(ComponentDetachEvent.class, listener,
  71. ComponentDetachListener.detachMethod);
  72. }
  73. /**
  74. * Fires the component attached event. This is called by the
  75. * {@link #setContent(Component)} method after the component has been set as
  76. * the content.
  77. *
  78. * @param component
  79. * the component that has been added to this container.
  80. */
  81. protected void fireComponentAttachEvent(Component component) {
  82. fireEvent(new ComponentAttachEvent(this, component));
  83. }
  84. /**
  85. * Fires the component detached event. This is called by the
  86. * {@link #setContent(Component)} method after the content component has
  87. * been replaced by other content.
  88. *
  89. * @param component
  90. * the component that has been removed from this container.
  91. */
  92. protected void fireComponentDetachEvent(Component component) {
  93. fireEvent(new ComponentDetachEvent(this, component));
  94. }
  95. @Override
  96. public Component getContent() {
  97. return content;
  98. }
  99. /**
  100. * Sets the content of this container. The content is a component that
  101. * serves as the outermost item of the visual contents.
  102. *
  103. * The content must always be set, either with a constructor parameter or by
  104. * calling this method.
  105. *
  106. * Previous versions of Vaadin used a {@link VerticalLayout} with margins
  107. * enabled as the default content but that is no longer the case.
  108. *
  109. * @param content
  110. * a component (typically a layout) to use as content
  111. */
  112. @Override
  113. public void setContent(Component content) {
  114. // Make sure we're not adding the component inside it's own content
  115. if (isOrHasAncestor(content)) {
  116. throw new IllegalArgumentException(
  117. "Component cannot be added inside it's own content");
  118. }
  119. Component oldContent = getContent();
  120. if (oldContent == content) {
  121. // do not set the same content twice
  122. return;
  123. }
  124. if (oldContent != null && equals(oldContent.getParent())) {
  125. oldContent.setParent(null);
  126. fireComponentDetachEvent(oldContent);
  127. }
  128. this.content = content;
  129. if (content != null) {
  130. removeFromParent(content);
  131. content.setParent(this);
  132. fireComponentAttachEvent(content);
  133. }
  134. markAsDirty();
  135. }
  136. /**
  137. * Utility method for removing a component from its parent (if possible).
  138. *
  139. * @param content
  140. * component to remove
  141. */
  142. // TODO move utility method elsewhere?
  143. public static void removeFromParent(Component content)
  144. throws IllegalArgumentException {
  145. // Verify the appropriate session is locked
  146. UI parentUI = content.getUI();
  147. if (parentUI != null) {
  148. VaadinSession parentSession = parentUI.getSession();
  149. if (parentSession != null && !parentSession.hasLock()) {
  150. String message = "Cannot remove from parent when the session is not locked.";
  151. if (VaadinService.isOtherSessionLocked(parentSession)) {
  152. message += " Furthermore, there is another locked session, indicating that the component might be about to be moved from one session to another.";
  153. }
  154. throw new IllegalStateException(message);
  155. }
  156. }
  157. HasComponents parent = content.getParent();
  158. if (parent instanceof ComponentContainer) {
  159. // If the component already has a parent, try to remove it
  160. ComponentContainer oldParent = (ComponentContainer) parent;
  161. oldParent.removeComponent(content);
  162. } else if (parent instanceof SingleComponentContainer) {
  163. SingleComponentContainer oldParent = (SingleComponentContainer) parent;
  164. if (oldParent.getContent() == content) {
  165. oldParent.setContent(null);
  166. }
  167. } else if (parent != null) {
  168. throw new IllegalArgumentException(
  169. "Content is already attached to another parent");
  170. }
  171. }
  172. // the setHeight()/setWidth() methods duplicated and simplified from
  173. // AbstractComponentContainer
  174. @Override
  175. public void setWidth(float width, Unit unit) {
  176. /*
  177. * child tree repaints may be needed, due to our fall back support for
  178. * invalid relative sizes
  179. */
  180. boolean dirtyChild = false;
  181. boolean childrenMayBecomeUndefined = false;
  182. if (getWidth() == SIZE_UNDEFINED && width != SIZE_UNDEFINED) {
  183. // children currently in invalid state may need repaint
  184. dirtyChild = getInvalidSizedChild(false);
  185. } else if ((width == SIZE_UNDEFINED && getWidth() != SIZE_UNDEFINED)
  186. || (unit == Unit.PERCENTAGE
  187. && getWidthUnits() != Unit.PERCENTAGE && !ComponentSizeValidator
  188. .parentCanDefineWidth(this))) {
  189. /*
  190. * relative width children may get to invalid state if width becomes
  191. * invalid. Width may also become invalid if units become percentage
  192. * due to the fallback support
  193. */
  194. childrenMayBecomeUndefined = true;
  195. dirtyChild = getInvalidSizedChild(false);
  196. }
  197. super.setWidth(width, unit);
  198. repaintChangedChildTree(dirtyChild, childrenMayBecomeUndefined, false);
  199. }
  200. private void repaintChangedChildTree(boolean invalidChild,
  201. boolean childrenMayBecomeUndefined, boolean vertical) {
  202. if (getContent() == null) {
  203. return;
  204. }
  205. boolean needRepaint = false;
  206. if (childrenMayBecomeUndefined) {
  207. // if became invalid now
  208. needRepaint = !invalidChild && getInvalidSizedChild(vertical);
  209. } else if (invalidChild) {
  210. // if not still invalid
  211. needRepaint = !getInvalidSizedChild(vertical);
  212. }
  213. if (needRepaint) {
  214. getContent().markAsDirtyRecursive();
  215. }
  216. }
  217. private boolean getInvalidSizedChild(final boolean vertical) {
  218. Component content = getContent();
  219. if (content == null) {
  220. return false;
  221. }
  222. if (vertical) {
  223. return !ComponentSizeValidator.checkHeights(content);
  224. } else {
  225. return !ComponentSizeValidator.checkWidths(content);
  226. }
  227. }
  228. @Override
  229. public void setHeight(float height, Unit unit) {
  230. /*
  231. * child tree repaints may be needed, due to our fall back support for
  232. * invalid relative sizes
  233. */
  234. boolean dirtyChild = false;
  235. boolean childrenMayBecomeUndefined = false;
  236. if (getHeight() == SIZE_UNDEFINED && height != SIZE_UNDEFINED) {
  237. // children currently in invalid state may need repaint
  238. dirtyChild = getInvalidSizedChild(true);
  239. } else if ((height == SIZE_UNDEFINED && getHeight() != SIZE_UNDEFINED)
  240. || (unit == Unit.PERCENTAGE
  241. && getHeightUnits() != Unit.PERCENTAGE && !ComponentSizeValidator
  242. .parentCanDefineHeight(this))) {
  243. /*
  244. * relative height children may get to invalid state if height
  245. * becomes invalid. Height may also become invalid if units become
  246. * percentage due to the fallback support.
  247. */
  248. childrenMayBecomeUndefined = true;
  249. dirtyChild = getInvalidSizedChild(true);
  250. }
  251. super.setHeight(height, unit);
  252. repaintChangedChildTree(dirtyChild, childrenMayBecomeUndefined, true);
  253. }
  254. /*
  255. * (non-Javadoc)
  256. *
  257. * @see com.vaadin.ui.AbstractComponent#readDesign(org.jsoup.nodes .Element,
  258. * com.vaadin.ui.declarative.DesignContext)
  259. */
  260. @Override
  261. public void readDesign(Element design, DesignContext designContext) {
  262. // process default attributes
  263. super.readDesign(design, designContext);
  264. // handle child element, checking that the design specifies at most one
  265. // child
  266. int childCount = design.children().size();
  267. if (childCount > 1) {
  268. throw new DesignException("The container of type "
  269. + getClass().toString()
  270. + " can have only one child component.");
  271. } else if (childCount == 1) {
  272. Element childElement = design.children().get(0);
  273. Component newChild = designContext.readDesign(childElement);
  274. setContent(newChild);
  275. }
  276. }
  277. /*
  278. * (non-Javadoc)
  279. *
  280. * @see com.vaadin.ui.AbstractComponent#writeDesign(org.jsoup.nodes.Element
  281. * , com.vaadin.ui.declarative.DesignContext)
  282. */
  283. @Override
  284. public void writeDesign(Element design, DesignContext designContext) {
  285. // write default attributes (also clears children and attributes)
  286. super.writeDesign(design, designContext);
  287. AbstractSingleComponentContainer def = designContext
  288. .getDefaultInstance(this);
  289. if (!designContext.shouldWriteChildren(this, def)) {
  290. return;
  291. }
  292. // handle child component
  293. Component child = getContent();
  294. if (child != null) {
  295. Element childNode = designContext.createElement(child);
  296. design.appendChild(childNode);
  297. }
  298. }
  299. }