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.

AbstractOrderedLayoutConnector.java 22KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628
  1. /*
  2. * Copyright 2000-2013 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.client.ui.orderedlayout;
  17. import java.util.List;
  18. import com.google.gwt.dom.client.Style.Unit;
  19. import com.google.gwt.user.client.Element;
  20. import com.vaadin.client.ApplicationConnection;
  21. import com.vaadin.client.ComponentConnector;
  22. import com.vaadin.client.ConnectorHierarchyChangeEvent;
  23. import com.vaadin.client.LayoutManager;
  24. import com.vaadin.client.Profiler;
  25. import com.vaadin.client.ServerConnector;
  26. import com.vaadin.client.Util;
  27. import com.vaadin.client.communication.StateChangeEvent;
  28. import com.vaadin.client.communication.StateChangeEvent.StateChangeHandler;
  29. import com.vaadin.client.ui.AbstractFieldConnector;
  30. import com.vaadin.client.ui.AbstractLayoutConnector;
  31. import com.vaadin.client.ui.LayoutClickEventHandler;
  32. import com.vaadin.client.ui.layout.ElementResizeEvent;
  33. import com.vaadin.client.ui.layout.ElementResizeListener;
  34. import com.vaadin.shared.AbstractFieldState;
  35. import com.vaadin.shared.ComponentConstants;
  36. import com.vaadin.shared.communication.URLReference;
  37. import com.vaadin.shared.ui.AlignmentInfo;
  38. import com.vaadin.shared.ui.LayoutClickRpc;
  39. import com.vaadin.shared.ui.MarginInfo;
  40. import com.vaadin.shared.ui.orderedlayout.AbstractOrderedLayoutServerRpc;
  41. import com.vaadin.shared.ui.orderedlayout.AbstractOrderedLayoutState;
  42. /**
  43. * Base class for vertical and horizontal ordered layouts
  44. */
  45. public abstract class AbstractOrderedLayoutConnector extends
  46. AbstractLayoutConnector {
  47. /*
  48. * Handlers & Listeners
  49. */
  50. private LayoutClickEventHandler clickEventHandler = new LayoutClickEventHandler(
  51. this) {
  52. @Override
  53. protected ComponentConnector getChildComponent(Element element) {
  54. return Util.getConnectorForElement(getConnection(), getWidget(),
  55. element);
  56. }
  57. @Override
  58. protected LayoutClickRpc getLayoutClickRPC() {
  59. return getRpcProxy(AbstractOrderedLayoutServerRpc.class);
  60. }
  61. };
  62. private StateChangeHandler childStateChangeHandler = new StateChangeHandler() {
  63. @Override
  64. public void onStateChanged(StateChangeEvent stateChangeEvent) {
  65. // Child state has changed, update stuff it hasn't already been done
  66. updateInternalState();
  67. /*
  68. * Some changes must always be done after each child's own state
  69. * change handler has been run because it might have changed some
  70. * styles that are overridden here.
  71. */
  72. ServerConnector child = stateChangeEvent.getConnector();
  73. if (child instanceof ComponentConnector) {
  74. ComponentConnector component = (ComponentConnector) child;
  75. Slot slot = getWidget().getSlot(component.getWidget());
  76. slot.setRelativeWidth(component.isRelativeWidth());
  77. slot.setRelativeHeight(component.isRelativeHeight());
  78. }
  79. }
  80. };
  81. private ElementResizeListener slotCaptionResizeListener = new ElementResizeListener() {
  82. @Override
  83. public void onElementResize(ElementResizeEvent e) {
  84. // Get all needed element references
  85. Element captionElement = (Element) e.getElement().cast();
  86. // Caption position determines if the widget element is the first or
  87. // last child inside the caption wrap
  88. CaptionPosition pos = getWidget().getCaptionPositionFromElement(
  89. (Element) captionElement.getParentElement().cast());
  90. // The default is the last child
  91. Element widgetElement = captionElement.getParentElement()
  92. .getLastChild().cast();
  93. // ...but if caption position is bottom or right, the widget is the
  94. // first child
  95. if (pos == CaptionPosition.BOTTOM || pos == CaptionPosition.RIGHT) {
  96. widgetElement = captionElement.getParentElement()
  97. .getFirstChildElement().cast();
  98. }
  99. if (captionElement == widgetElement) {
  100. // Caption element already detached
  101. Slot slot = getWidget().getSlot(widgetElement);
  102. if (slot != null) {
  103. slot.setCaptionResizeListener(null);
  104. }
  105. return;
  106. }
  107. String widgetWidth = widgetElement.getStyle().getWidth();
  108. String widgetHeight = widgetElement.getStyle().getHeight();
  109. if (widgetHeight.endsWith("%")
  110. && (pos == CaptionPosition.TOP || pos == CaptionPosition.BOTTOM)) {
  111. getWidget().updateCaptionOffset(captionElement);
  112. } else if (widgetWidth.endsWith("%")
  113. && (pos == CaptionPosition.LEFT || pos == CaptionPosition.RIGHT)) {
  114. getWidget().updateCaptionOffset(captionElement);
  115. }
  116. updateLayoutHeight();
  117. if (needsExpand()) {
  118. getWidget().updateExpandCompensation();
  119. }
  120. }
  121. };
  122. private ElementResizeListener childComponentResizeListener = new ElementResizeListener() {
  123. @Override
  124. public void onElementResize(ElementResizeEvent e) {
  125. updateLayoutHeight();
  126. if (needsExpand()) {
  127. getWidget().updateExpandCompensation();
  128. }
  129. }
  130. };
  131. private ElementResizeListener spacingResizeListener = new ElementResizeListener() {
  132. @Override
  133. public void onElementResize(ElementResizeEvent e) {
  134. if (needsExpand()) {
  135. getWidget().updateExpandCompensation();
  136. }
  137. }
  138. };
  139. /*
  140. * (non-Javadoc)
  141. *
  142. * @see com.vaadin.client.ui.AbstractComponentConnector#init()
  143. */
  144. @Override
  145. public void init() {
  146. super.init();
  147. getWidget().setLayoutManager(getLayoutManager());
  148. }
  149. /*
  150. * (non-Javadoc)
  151. *
  152. * @see com.vaadin.client.ui.AbstractLayoutConnector#getState()
  153. */
  154. @Override
  155. public AbstractOrderedLayoutState getState() {
  156. return (AbstractOrderedLayoutState) super.getState();
  157. }
  158. /*
  159. * (non-Javadoc)
  160. *
  161. * @see com.vaadin.client.ui.AbstractComponentConnector#getWidget()
  162. */
  163. @Override
  164. public VAbstractOrderedLayout getWidget() {
  165. return (VAbstractOrderedLayout) super.getWidget();
  166. }
  167. /**
  168. * Keep track of whether any child has relative height. Used to determine
  169. * whether measurements are needed to make relative child heights work
  170. * together with undefined container height.
  171. */
  172. private boolean hasChildrenWithRelativeHeight = false;
  173. /**
  174. * Keep track of whether any child is middle aligned. Used to determine if
  175. * measurements are needed to make middle aligned children work.
  176. */
  177. private boolean hasChildrenWithMiddleAlignment = false;
  178. /**
  179. * Keeps track of whether slots should be expanded based on available space.
  180. */
  181. private boolean needsExpand = false;
  182. /**
  183. * The id of the previous response for which state changes have been
  184. * processed. If this is the same as the
  185. * {@link ApplicationConnection#getLastResponseId()}, it means that we can
  186. * skip some quite expensive calculations because we know that the state
  187. * hasn't changed since the last time the values were calculated.
  188. */
  189. private int processedResponseId = -1;
  190. /*
  191. * (non-Javadoc)
  192. *
  193. * @see com.vaadin.client.HasComponentsConnector#updateCaption(com.vaadin
  194. * .client.ComponentConnector)
  195. */
  196. @Override
  197. public void updateCaption(ComponentConnector connector) {
  198. /*
  199. * Don't directly update captions here to avoid calling e.g.
  200. * updateLayoutHeight() before everything is initialized.
  201. * updateInternalState() will ensure all captions are updated when
  202. * appropriate.
  203. */
  204. updateInternalState();
  205. }
  206. private void updateCaptionInternal(ComponentConnector child) {
  207. Slot slot = getWidget().getSlot(child.getWidget());
  208. String caption = child.getState().caption;
  209. URLReference iconUrl = child.getState().resources
  210. .get(ComponentConstants.ICON_RESOURCE);
  211. String iconUrlString = iconUrl != null ? iconUrl.getURL() : null;
  212. List<String> styles = child.getState().styles;
  213. String error = child.getState().errorMessage;
  214. boolean showError = error != null;
  215. if (child.getState() instanceof AbstractFieldState) {
  216. AbstractFieldState abstractFieldState = (AbstractFieldState) child
  217. .getState();
  218. showError = showError && !abstractFieldState.hideErrors;
  219. }
  220. boolean required = false;
  221. if (child instanceof AbstractFieldConnector) {
  222. required = ((AbstractFieldConnector) child).isRequired();
  223. }
  224. boolean enabled = child.isEnabled();
  225. slot.setCaption(caption, iconUrlString, styles, error, showError,
  226. required, enabled);
  227. if (slot.hasCaption()) {
  228. CaptionPosition pos = slot.getCaptionPosition();
  229. getLayoutManager().addElementResizeListener(
  230. slot.getCaptionElement(), slotCaptionResizeListener);
  231. if (child.isRelativeHeight()
  232. && (pos == CaptionPosition.TOP || pos == CaptionPosition.BOTTOM)) {
  233. getWidget().updateCaptionOffset(slot.getCaptionElement());
  234. } else if (child.isRelativeWidth()
  235. && (pos == CaptionPosition.LEFT || pos == CaptionPosition.RIGHT)) {
  236. getWidget().updateCaptionOffset(slot.getCaptionElement());
  237. }
  238. }
  239. }
  240. /*
  241. * (non-Javadoc)
  242. *
  243. * @see com.vaadin.client.ui.AbstractComponentContainerConnector#
  244. * onConnectorHierarchyChange
  245. * (com.vaadin.client.ConnectorHierarchyChangeEvent)
  246. */
  247. @Override
  248. public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) {
  249. Profiler.enter("AOLC.onConnectorHierarchyChange");
  250. List<ComponentConnector> previousChildren = event.getOldChildren();
  251. int currentIndex = 0;
  252. VAbstractOrderedLayout layout = getWidget();
  253. layout.setSpacing(getState().spacing);
  254. for (ComponentConnector child : getChildComponents()) {
  255. Profiler.enter("AOLC.onConnectorHierarchyChange add children");
  256. Slot slot = layout.getSlot(child.getWidget());
  257. if (slot.getParent() != layout) {
  258. Profiler.enter("AOLC.onConnectorHierarchyChange add state change handler");
  259. child.addStateChangeHandler(childStateChangeHandler);
  260. Profiler.leave("AOLC.onConnectorHierarchyChange add state change handler");
  261. }
  262. Profiler.enter("AOLC.onConnectorHierarchyChange addOrMoveSlot");
  263. layout.addOrMoveSlot(slot, currentIndex++);
  264. Profiler.leave("AOLC.onConnectorHierarchyChange addOrMoveSlot");
  265. Profiler.leave("AOLC.onConnectorHierarchyChange add children");
  266. }
  267. for (ComponentConnector child : previousChildren) {
  268. Profiler.enter("AOLC.onConnectorHierarchyChange remove children");
  269. if (child.getParent() != this) {
  270. Slot slot = layout.getSlot(child.getWidget());
  271. slot.setWidgetResizeListener(null);
  272. if (slot.hasCaption()) {
  273. slot.setCaptionResizeListener(null);
  274. }
  275. if (slot.getSpacingElement() != null) {
  276. slot.setSpacingResizeListener(null);
  277. }
  278. child.removeStateChangeHandler(childStateChangeHandler);
  279. layout.removeWidget(child.getWidget());
  280. }
  281. Profiler.leave("AOL.onConnectorHierarchyChange remove children");
  282. }
  283. Profiler.leave("AOLC.onConnectorHierarchyChange");
  284. updateInternalState();
  285. }
  286. /*
  287. * (non-Javadoc)
  288. *
  289. * @see
  290. * com.vaadin.client.ui.AbstractComponentConnector#onStateChanged(com.vaadin
  291. * .client.communication.StateChangeEvent)
  292. */
  293. @Override
  294. public void onStateChanged(StateChangeEvent stateChangeEvent) {
  295. super.onStateChanged(stateChangeEvent);
  296. clickEventHandler.handleEventHandlerRegistration();
  297. getWidget().setMargin(new MarginInfo(getState().marginsBitmask));
  298. getWidget().setSpacing(getState().spacing);
  299. updateInternalState();
  300. }
  301. /**
  302. * Updates DOM properties and listeners based on the current state of this
  303. * layout and its children.
  304. */
  305. private void updateInternalState() {
  306. // Avoid updating again for the same data
  307. int lastResponseId = getConnection().getLastResponseId();
  308. if (processedResponseId == lastResponseId) {
  309. return;
  310. }
  311. Profiler.enter("AOLC.updateInternalState");
  312. // Remember that everything is updated for this response
  313. processedResponseId = lastResponseId;
  314. hasChildrenWithRelativeHeight = false;
  315. hasChildrenWithMiddleAlignment = false;
  316. needsExpand = getWidget().vertical ? !isUndefinedHeight()
  317. : !isUndefinedWidth();
  318. boolean onlyZeroExpands = true;
  319. if (needsExpand) {
  320. for (ComponentConnector child : getChildComponents()) {
  321. double expandRatio = getState().childData.get(child).expandRatio;
  322. if (expandRatio != 0) {
  323. onlyZeroExpands = false;
  324. break;
  325. }
  326. }
  327. }
  328. // First update bookkeeping for all children
  329. for (ComponentConnector child : getChildComponents()) {
  330. Slot slot = getWidget().getSlot(child.getWidget());
  331. slot.setRelativeWidth(child.isRelativeWidth());
  332. slot.setRelativeHeight(child.isRelativeHeight());
  333. if (child.delegateCaptionHandling()) {
  334. updateCaptionInternal(child);
  335. }
  336. // Update slot style names
  337. List<String> childStyles = child.getState().styles;
  338. if (childStyles == null) {
  339. getWidget().setSlotStyleNames(child.getWidget(),
  340. (String[]) null);
  341. } else {
  342. getWidget().setSlotStyleNames(child.getWidget(),
  343. childStyles.toArray(new String[childStyles.size()]));
  344. }
  345. AlignmentInfo alignment = new AlignmentInfo(
  346. getState().childData.get(child).alignmentBitmask);
  347. slot.setAlignment(alignment);
  348. if (alignment.isVerticalCenter()) {
  349. hasChildrenWithMiddleAlignment = true;
  350. }
  351. double expandRatio = onlyZeroExpands ? 1 : getState().childData
  352. .get(child).expandRatio;
  353. slot.setExpandRatio(expandRatio);
  354. if (child.isRelativeHeight()) {
  355. hasChildrenWithRelativeHeight = true;
  356. }
  357. }
  358. if (needsFixedHeight()) {
  359. // Add resize listener to ensure the widget itself is measured
  360. getLayoutManager().addElementResizeListener(
  361. getWidget().getElement(), childComponentResizeListener);
  362. } else {
  363. getLayoutManager().removeElementResizeListener(
  364. getWidget().getElement(), childComponentResizeListener);
  365. }
  366. // Then update listeners based on bookkeeping
  367. updateAllSlotListeners();
  368. // Update the layout at this point to ensure it's OK even if we get no
  369. // element resize events
  370. updateLayoutHeight();
  371. if (needsExpand()) {
  372. getWidget().updateExpandedSizes();
  373. getWidget().updateExpandCompensation();
  374. } else {
  375. getWidget().clearExpand();
  376. }
  377. Profiler.leave("AOLC.updateInternalState");
  378. }
  379. /**
  380. * Does the layout need a fixed height?
  381. */
  382. private boolean needsFixedHeight() {
  383. boolean isVertical = getWidget().vertical;
  384. if (isVertical) {
  385. // Doesn't need height fix for vertical layouts
  386. return false;
  387. }
  388. else if (!isUndefinedHeight()) {
  389. // Fix not needed unless the height is undefined
  390. return false;
  391. }
  392. else if (!hasChildrenWithRelativeHeight
  393. && !hasChildrenWithMiddleAlignment) {
  394. // Already works if there are no relative heights or middle aligned
  395. // children
  396. return false;
  397. }
  398. return true;
  399. }
  400. /**
  401. * Does the layout need to expand?
  402. */
  403. private boolean needsExpand() {
  404. return needsExpand;
  405. }
  406. /**
  407. * Add slot listeners
  408. */
  409. private void updateAllSlotListeners() {
  410. for (ComponentConnector child : getChildComponents()) {
  411. updateSlotListeners(child);
  412. }
  413. }
  414. /**
  415. * Add/remove necessary ElementResizeListeners for one slot. This should be
  416. * called after each update to the slot's or it's widget.
  417. */
  418. private void updateSlotListeners(ComponentConnector child) {
  419. Slot slot = getWidget().getSlot(child.getWidget());
  420. // Clear all possible listeners first
  421. slot.setWidgetResizeListener(null);
  422. if (slot.hasCaption()) {
  423. slot.setCaptionResizeListener(null);
  424. }
  425. if (slot.hasSpacing()) {
  426. slot.setSpacingResizeListener(null);
  427. }
  428. // Add all necessary listeners
  429. if (needsFixedHeight()) {
  430. slot.setWidgetResizeListener(childComponentResizeListener);
  431. if (slot.hasCaption()) {
  432. slot.setCaptionResizeListener(slotCaptionResizeListener);
  433. }
  434. } else if ((child.isRelativeHeight() || child.isRelativeWidth())
  435. && slot.hasCaption()) {
  436. /*
  437. * If the slot has caption, we need to listen for its size changes
  438. * in order to update the padding/margin offset for relative sized
  439. * components.
  440. *
  441. * TODO might only be needed if the caption is in the same direction
  442. * as the relative size?
  443. */
  444. slot.setCaptionResizeListener(slotCaptionResizeListener);
  445. }
  446. if (needsExpand()) {
  447. // TODO widget resize only be needed for children without expand?
  448. slot.setWidgetResizeListener(childComponentResizeListener);
  449. if (slot.hasSpacing()) {
  450. slot.setSpacingResizeListener(spacingResizeListener);
  451. }
  452. }
  453. }
  454. /**
  455. * Re-calculate the layout height
  456. */
  457. private void updateLayoutHeight() {
  458. if (needsFixedHeight()) {
  459. int h = getMaxHeight();
  460. if (h < 0) {
  461. // Postpone change if there are elements that have not yet been
  462. // measured
  463. return;
  464. }
  465. h += getLayoutManager().getBorderHeight(getWidget().getElement())
  466. + getLayoutManager().getPaddingHeight(
  467. getWidget().getElement());
  468. getWidget().getElement().getStyle().setHeight(h, Unit.PX);
  469. getLayoutManager().setNeedsMeasure(this);
  470. }
  471. }
  472. /**
  473. * Measures the maximum height of the layout in pixels
  474. */
  475. private int getMaxHeight() {
  476. int highestNonRelative = -1;
  477. int highestRelative = -1;
  478. LayoutManager layoutManager = getLayoutManager();
  479. for (ComponentConnector child : getChildComponents()) {
  480. Slot slot = getWidget().getSlot(child.getWidget());
  481. Element captionElement = slot.getCaptionElement();
  482. CaptionPosition pos = slot.getCaptionPosition();
  483. Element childElement = child.getWidget().getElement();
  484. int h = layoutManager.getOuterHeight(childElement);
  485. if (h == -1) {
  486. // Height has not yet been measured -> postpone actions that
  487. // depend on the max height
  488. return -1;
  489. }
  490. boolean hasRelativeHeight = slot.hasRelativeHeight();
  491. boolean includeCaptionHeight = captionElement != null
  492. && (pos == CaptionPosition.TOP || pos == CaptionPosition.BOTTOM);
  493. if (!hasRelativeHeight && !includeCaptionHeight
  494. && captionElement != null) {
  495. String sHeight = childElement.getStyle().getHeight();
  496. includeCaptionHeight = (sHeight == null || !sHeight
  497. .endsWith("%"));
  498. }
  499. if (includeCaptionHeight) {
  500. int captionHeight = layoutManager
  501. .getOuterHeight(captionElement)
  502. - getLayoutManager().getMarginHeight(captionElement);
  503. if (captionHeight == -1) {
  504. // Height has not yet been measured -> postpone actions that
  505. // depend on the max height
  506. return -1;
  507. }
  508. h += captionHeight;
  509. }
  510. if (!hasRelativeHeight) {
  511. if (h > highestNonRelative) {
  512. highestNonRelative = h;
  513. }
  514. } else {
  515. if (h > highestRelative) {
  516. highestRelative = h;
  517. }
  518. }
  519. }
  520. return highestNonRelative > -1 ? highestNonRelative : highestRelative;
  521. }
  522. /*
  523. * (non-Javadoc)
  524. *
  525. * @see com.vaadin.client.ui.AbstractComponentConnector#onUnregister()
  526. */
  527. @Override
  528. public void onUnregister() {
  529. // Cleanup all ElementResizeListeners
  530. for (ComponentConnector child : getChildComponents()) {
  531. Slot slot = getWidget().getSlot(child.getWidget());
  532. if (slot.hasCaption()) {
  533. slot.setCaptionResizeListener(null);
  534. }
  535. if (slot.getSpacingElement() != null) {
  536. slot.setSpacingResizeListener(null);
  537. }
  538. slot.setWidgetResizeListener(null);
  539. }
  540. super.onUnregister();
  541. }
  542. }