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 13KB

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