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.

VPanel.java 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
  1. /*
  2. @VaadinApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.terminal.gwt.client.ui;
  5. import java.util.Set;
  6. import com.google.gwt.dom.client.DivElement;
  7. import com.google.gwt.dom.client.Document;
  8. import com.google.gwt.event.dom.client.DomEvent.Type;
  9. import com.google.gwt.event.dom.client.TouchStartEvent;
  10. import com.google.gwt.event.dom.client.TouchStartHandler;
  11. import com.google.gwt.event.shared.EventHandler;
  12. import com.google.gwt.event.shared.HandlerRegistration;
  13. import com.google.gwt.user.client.DOM;
  14. import com.google.gwt.user.client.Element;
  15. import com.google.gwt.user.client.Event;
  16. import com.google.gwt.user.client.ui.SimplePanel;
  17. import com.google.gwt.user.client.ui.Widget;
  18. import com.vaadin.terminal.gwt.client.ApplicationConnection;
  19. import com.vaadin.terminal.gwt.client.BrowserInfo;
  20. import com.vaadin.terminal.gwt.client.Container;
  21. import com.vaadin.terminal.gwt.client.Focusable;
  22. import com.vaadin.terminal.gwt.client.RenderInformation;
  23. import com.vaadin.terminal.gwt.client.RenderSpace;
  24. import com.vaadin.terminal.gwt.client.UIDL;
  25. import com.vaadin.terminal.gwt.client.Util;
  26. import com.vaadin.terminal.gwt.client.VPaintableMap;
  27. import com.vaadin.terminal.gwt.client.VPaintableWidget;
  28. import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner;
  29. public class VPanel extends SimplePanel implements Container,
  30. ShortcutActionHandlerOwner, Focusable {
  31. public static final String CLICK_EVENT_IDENTIFIER = "click";
  32. public static final String CLASSNAME = "v-panel";
  33. ApplicationConnection client;
  34. String id;
  35. private final Element captionNode = DOM.createDiv();
  36. private final Element captionText = DOM.createSpan();
  37. private Icon icon;
  38. private final Element bottomDecoration = DOM.createDiv();
  39. private final Element contentNode = DOM.createDiv();
  40. private Element errorIndicatorElement;
  41. private String height;
  42. private VPaintableWidget layout;
  43. ShortcutActionHandler shortcutHandler;
  44. private String width = "";
  45. private Element geckoCaptionMeter;
  46. private int scrollTop;
  47. private int scrollLeft;
  48. private RenderInformation renderInformation = new RenderInformation();
  49. private int borderPaddingHorizontal = -1;
  50. private int borderPaddingVertical = -1;
  51. private int captionPaddingHorizontal = -1;
  52. private int captionMarginLeft = -1;
  53. private boolean rendering;
  54. private int contentMarginLeft = -1;
  55. private String previousStyleName;
  56. private ClickEventHandler clickEventHandler = new ClickEventHandler(this,
  57. CLICK_EVENT_IDENTIFIER) {
  58. @Override
  59. protected <H extends EventHandler> HandlerRegistration registerHandler(
  60. H handler, Type<H> type) {
  61. return addDomHandler(handler, type);
  62. }
  63. };
  64. private TouchScrollDelegate touchScrollDelegate;
  65. public VPanel() {
  66. super();
  67. DivElement captionWrap = Document.get().createDivElement();
  68. captionWrap.appendChild(captionNode);
  69. captionNode.appendChild(captionText);
  70. captionWrap.setClassName(CLASSNAME + "-captionwrap");
  71. captionNode.setClassName(CLASSNAME + "-caption");
  72. contentNode.setClassName(CLASSNAME + "-content");
  73. bottomDecoration.setClassName(CLASSNAME + "-deco");
  74. getElement().appendChild(captionWrap);
  75. /*
  76. * Make contentNode focusable only by using the setFocus() method. This
  77. * behaviour can be changed by invoking setTabIndex() in the serverside
  78. * implementation
  79. */
  80. contentNode.setTabIndex(-1);
  81. getElement().appendChild(contentNode);
  82. getElement().appendChild(bottomDecoration);
  83. setStyleName(CLASSNAME);
  84. DOM.sinkEvents(getElement(), Event.ONKEYDOWN);
  85. DOM.sinkEvents(contentNode, Event.ONSCROLL | Event.TOUCHEVENTS);
  86. contentNode.getStyle().setProperty("position", "relative");
  87. getElement().getStyle().setProperty("overflow", "hidden");
  88. addHandler(new TouchStartHandler() {
  89. public void onTouchStart(TouchStartEvent event) {
  90. getTouchScrollDelegate().onTouchStart(event);
  91. }
  92. }, TouchStartEvent.getType());
  93. }
  94. /**
  95. * Sets the keyboard focus on the Panel
  96. *
  97. * @param focus
  98. * Should the panel have focus or not.
  99. */
  100. public void setFocus(boolean focus) {
  101. if (focus) {
  102. getContainerElement().focus();
  103. } else {
  104. getContainerElement().blur();
  105. }
  106. }
  107. /*
  108. * (non-Javadoc)
  109. *
  110. * @see com.vaadin.terminal.gwt.client.Focusable#focus()
  111. */
  112. public void focus() {
  113. setFocus(true);
  114. }
  115. @Override
  116. protected Element getContainerElement() {
  117. return contentNode;
  118. }
  119. private void setCaption(String text) {
  120. DOM.setInnerHTML(captionText, text);
  121. }
  122. public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
  123. rendering = true;
  124. if (!uidl.hasAttribute("cached")) {
  125. // Handle caption displaying and style names, prior generics.
  126. // Affects size
  127. // calculations
  128. // Restore default stylenames
  129. contentNode.setClassName(CLASSNAME + "-content");
  130. bottomDecoration.setClassName(CLASSNAME + "-deco");
  131. captionNode.setClassName(CLASSNAME + "-caption");
  132. boolean hasCaption = false;
  133. if (uidl.hasAttribute("caption")
  134. && !uidl.getStringAttribute("caption").equals("")) {
  135. setCaption(uidl.getStringAttribute("caption"));
  136. hasCaption = true;
  137. } else {
  138. setCaption("");
  139. captionNode.setClassName(CLASSNAME + "-nocaption");
  140. }
  141. // Add proper stylenames for all elements. This way we can prevent
  142. // unwanted CSS selector inheritance.
  143. if (uidl.hasAttribute("style")) {
  144. final String[] styles = uidl.getStringAttribute("style").split(
  145. " ");
  146. final String captionBaseClass = CLASSNAME
  147. + (hasCaption ? "-caption" : "-nocaption");
  148. final String contentBaseClass = CLASSNAME + "-content";
  149. final String decoBaseClass = CLASSNAME + "-deco";
  150. String captionClass = captionBaseClass;
  151. String contentClass = contentBaseClass;
  152. String decoClass = decoBaseClass;
  153. for (int i = 0; i < styles.length; i++) {
  154. captionClass += " " + captionBaseClass + "-" + styles[i];
  155. contentClass += " " + contentBaseClass + "-" + styles[i];
  156. decoClass += " " + decoBaseClass + "-" + styles[i];
  157. }
  158. captionNode.setClassName(captionClass);
  159. contentNode.setClassName(contentClass);
  160. bottomDecoration.setClassName(decoClass);
  161. }
  162. }
  163. // Ensure correct implementation
  164. if (client.updateComponent(this, uidl, false)) {
  165. rendering = false;
  166. return;
  167. }
  168. clickEventHandler.handleEventHandlerRegistration(client);
  169. this.client = client;
  170. id = uidl.getId();
  171. setIconUri(uidl, client);
  172. handleError(uidl);
  173. // Render content
  174. final UIDL layoutUidl = uidl.getChildUIDL(0);
  175. final VPaintableWidget newLayout = client.getPaintable(layoutUidl);
  176. if (newLayout != layout) {
  177. if (layout != null) {
  178. client.unregisterPaintable(layout);
  179. }
  180. setWidget(newLayout.getWidgetForPaintable());
  181. layout = newLayout;
  182. }
  183. layout.updateFromUIDL(layoutUidl, client);
  184. // We may have actions attached to this panel
  185. if (uidl.getChildCount() > 1) {
  186. final int cnt = uidl.getChildCount();
  187. for (int i = 1; i < cnt; i++) {
  188. UIDL childUidl = uidl.getChildUIDL(i);
  189. if (childUidl.getTag().equals("actions")) {
  190. if (shortcutHandler == null) {
  191. shortcutHandler = new ShortcutActionHandler(id, client);
  192. }
  193. shortcutHandler.updateActionMap(childUidl);
  194. }
  195. }
  196. }
  197. if (uidl.hasVariable("scrollTop")
  198. && uidl.getIntVariable("scrollTop") != scrollTop) {
  199. scrollTop = uidl.getIntVariable("scrollTop");
  200. contentNode.setScrollTop(scrollTop);
  201. // re-read the actual scrollTop in case invalid value was set
  202. // (scrollTop != 0 when no scrollbar exists, other values would be
  203. // caught by scroll listener), see #3784
  204. scrollTop = contentNode.getScrollTop();
  205. }
  206. if (uidl.hasVariable("scrollLeft")
  207. && uidl.getIntVariable("scrollLeft") != scrollLeft) {
  208. scrollLeft = uidl.getIntVariable("scrollLeft");
  209. contentNode.setScrollLeft(scrollLeft);
  210. // re-read the actual scrollTop in case invalid value was set
  211. // (scrollTop != 0 when no scrollbar exists, other values would be
  212. // caught by scroll listener), see #3784
  213. scrollLeft = contentNode.getScrollLeft();
  214. }
  215. // Must be run after scrollTop is set as Webkit overflow fix re-sets the
  216. // scrollTop
  217. runHacks(false);
  218. // And apply tab index
  219. if (uidl.hasVariable("tabindex")) {
  220. contentNode.setTabIndex(uidl.getIntVariable("tabindex"));
  221. }
  222. rendering = false;
  223. }
  224. @Override
  225. public void setStyleName(String style) {
  226. if (!style.equals(previousStyleName)) {
  227. super.setStyleName(style);
  228. detectContainerBorders();
  229. previousStyleName = style;
  230. }
  231. }
  232. private void handleError(UIDL uidl) {
  233. if (uidl.hasAttribute("error")) {
  234. if (errorIndicatorElement == null) {
  235. errorIndicatorElement = DOM.createSpan();
  236. DOM.setElementProperty(errorIndicatorElement, "className",
  237. "v-errorindicator");
  238. DOM.sinkEvents(errorIndicatorElement, Event.MOUSEEVENTS);
  239. sinkEvents(Event.MOUSEEVENTS);
  240. }
  241. DOM.insertBefore(captionNode, errorIndicatorElement, captionText);
  242. } else if (errorIndicatorElement != null) {
  243. DOM.removeChild(captionNode, errorIndicatorElement);
  244. errorIndicatorElement = null;
  245. }
  246. }
  247. private void setIconUri(UIDL uidl, ApplicationConnection client) {
  248. final String iconUri = uidl.hasAttribute("icon") ? uidl
  249. .getStringAttribute("icon") : null;
  250. if (iconUri == null) {
  251. if (icon != null) {
  252. DOM.removeChild(captionNode, icon.getElement());
  253. icon = null;
  254. }
  255. } else {
  256. if (icon == null) {
  257. icon = new Icon(client);
  258. DOM.insertChild(captionNode, icon.getElement(), 0);
  259. }
  260. icon.setUri(iconUri);
  261. }
  262. }
  263. public void runHacks(boolean runGeckoFix) {
  264. if ((BrowserInfo.get().isIE()) && (width == null || width.equals(""))) {
  265. /*
  266. * IE (what version??) needs width to be specified for the root DIV
  267. * so we calculate that from the sizes of the caption and layout
  268. */
  269. int captionWidth = captionText.getOffsetWidth()
  270. + getCaptionMarginLeft() + getCaptionPaddingHorizontal();
  271. int layoutWidth = layout.getWidgetForPaintable().getOffsetWidth()
  272. + getContainerBorderWidth();
  273. int width = layoutWidth;
  274. if (captionWidth > width) {
  275. width = captionWidth;
  276. }
  277. super.setWidth(width + "px");
  278. }
  279. if (runGeckoFix && BrowserInfo.get().isGecko()) {
  280. // workaround for #1764
  281. if (width == null || width.equals("")) {
  282. if (geckoCaptionMeter == null) {
  283. geckoCaptionMeter = DOM.createDiv();
  284. DOM.appendChild(captionNode, geckoCaptionMeter);
  285. }
  286. int captionWidth = DOM.getElementPropertyInt(captionText,
  287. "offsetWidth");
  288. int availWidth = DOM.getElementPropertyInt(geckoCaptionMeter,
  289. "offsetWidth");
  290. if (captionWidth == availWidth) {
  291. /*
  292. * Caption width defines panel width -> Gecko based browsers
  293. * somehow fails to float things right, without the
  294. * "noncode" below
  295. */
  296. setWidth(getOffsetWidth() + "px");
  297. } else {
  298. DOM.setStyleAttribute(captionNode, "width", "");
  299. }
  300. }
  301. }
  302. client.runDescendentsLayout(this);
  303. Util.runWebkitOverflowAutoFix(contentNode);
  304. }
  305. @Override
  306. public void onBrowserEvent(Event event) {
  307. super.onBrowserEvent(event);
  308. final Element target = DOM.eventGetTarget(event);
  309. final int type = DOM.eventGetType(event);
  310. if (type == Event.ONKEYDOWN && shortcutHandler != null) {
  311. shortcutHandler.handleKeyboardEvent(event);
  312. return;
  313. }
  314. if (type == Event.ONSCROLL) {
  315. int newscrollTop = DOM.getElementPropertyInt(contentNode,
  316. "scrollTop");
  317. int newscrollLeft = DOM.getElementPropertyInt(contentNode,
  318. "scrollLeft");
  319. if (client != null
  320. && (newscrollLeft != scrollLeft || newscrollTop != scrollTop)) {
  321. scrollLeft = newscrollLeft;
  322. scrollTop = newscrollTop;
  323. client.updateVariable(id, "scrollTop", scrollTop, false);
  324. client.updateVariable(id, "scrollLeft", scrollLeft, false);
  325. }
  326. } else if (captionNode.isOrHasChild(target)) {
  327. if (client != null) {
  328. client.handleTooltipEvent(event, this);
  329. }
  330. }
  331. }
  332. protected TouchScrollDelegate getTouchScrollDelegate() {
  333. if (touchScrollDelegate == null) {
  334. touchScrollDelegate = new TouchScrollDelegate(contentNode);
  335. }
  336. return touchScrollDelegate;
  337. }
  338. @Override
  339. public void setHeight(String height) {
  340. this.height = height;
  341. super.setHeight(height);
  342. if (height != null && !"".equals(height)) {
  343. final int targetHeight = getOffsetHeight();
  344. int containerHeight = targetHeight
  345. - captionNode.getParentElement().getOffsetHeight()
  346. - bottomDecoration.getOffsetHeight()
  347. - getContainerBorderHeight();
  348. if (containerHeight < 0) {
  349. containerHeight = 0;
  350. }
  351. DOM.setStyleAttribute(contentNode, "height", containerHeight + "px");
  352. } else {
  353. DOM.setStyleAttribute(contentNode, "height", "");
  354. }
  355. if (!rendering) {
  356. runHacks(true);
  357. }
  358. }
  359. private int getCaptionMarginLeft() {
  360. if (captionMarginLeft < 0) {
  361. detectContainerBorders();
  362. }
  363. return captionMarginLeft;
  364. }
  365. private int getContentMarginLeft() {
  366. if (contentMarginLeft < 0) {
  367. detectContainerBorders();
  368. }
  369. return contentMarginLeft;
  370. }
  371. private int getCaptionPaddingHorizontal() {
  372. if (captionPaddingHorizontal < 0) {
  373. detectContainerBorders();
  374. }
  375. return captionPaddingHorizontal;
  376. }
  377. private int getContainerBorderHeight() {
  378. if (borderPaddingVertical < 0) {
  379. detectContainerBorders();
  380. }
  381. return borderPaddingVertical;
  382. }
  383. @Override
  384. public void setWidth(String width) {
  385. if (this.width.equals(width)) {
  386. return;
  387. }
  388. this.width = width;
  389. super.setWidth(width);
  390. if (!rendering) {
  391. runHacks(true);
  392. if (height.equals("")) {
  393. // Width change may affect height
  394. Util.updateRelativeChildrenAndSendSizeUpdateEvent(client, this,
  395. this);
  396. }
  397. }
  398. }
  399. private int getContainerBorderWidth() {
  400. if (borderPaddingHorizontal < 0) {
  401. detectContainerBorders();
  402. }
  403. return borderPaddingHorizontal;
  404. }
  405. private void detectContainerBorders() {
  406. DOM.setStyleAttribute(contentNode, "overflow", "hidden");
  407. borderPaddingHorizontal = Util.measureHorizontalBorder(contentNode);
  408. borderPaddingVertical = Util.measureVerticalBorder(contentNode);
  409. DOM.setStyleAttribute(contentNode, "overflow", "auto");
  410. captionPaddingHorizontal = Util.measureHorizontalPaddingAndBorder(
  411. captionNode, 26);
  412. captionMarginLeft = Util.measureMarginLeft(captionNode);
  413. contentMarginLeft = Util.measureMarginLeft(contentNode);
  414. }
  415. public boolean hasChildComponent(Widget component) {
  416. if (component != null && component == layout) {
  417. return true;
  418. } else {
  419. return false;
  420. }
  421. }
  422. public void replaceChildComponent(Widget oldComponent, Widget newComponent) {
  423. // TODO This is untested as no layouts require this
  424. if (oldComponent != layout.getWidgetForPaintable()) {
  425. return;
  426. }
  427. setWidget(newComponent);
  428. layout = VPaintableMap.get(client).getPaintable(newComponent);
  429. }
  430. public RenderSpace getAllocatedSpace(Widget child) {
  431. int w = 0;
  432. int h = 0;
  433. if (width != null && !width.equals("")) {
  434. w = getOffsetWidth() - getContainerBorderWidth();
  435. if (w < 0) {
  436. w = 0;
  437. }
  438. }
  439. if (height != null && !height.equals("")) {
  440. h = contentNode.getOffsetHeight() - getContainerBorderHeight();
  441. if (h < 0) {
  442. h = 0;
  443. }
  444. }
  445. return new RenderSpace(w, h, true);
  446. }
  447. public boolean requestLayout(Set<Widget> children) {
  448. // content size change might cause change to its available space
  449. // (scrollbars)
  450. client.handleComponentRelativeSize(layout.getWidgetForPaintable());
  451. if (height != null && height != "" && width != null && width != "") {
  452. /*
  453. * If the height and width has been specified the child components
  454. * cannot make the size of the layout change
  455. */
  456. return true;
  457. }
  458. runHacks(false);
  459. return !renderInformation.updateSize(getElement());
  460. }
  461. public void updateCaption(VPaintableWidget component, UIDL uidl) {
  462. // NOP: layouts caption, errors etc not rendered in Panel
  463. }
  464. @Override
  465. protected void onAttach() {
  466. super.onAttach();
  467. detectContainerBorders();
  468. }
  469. public ShortcutActionHandler getShortcutActionHandler() {
  470. return shortcutHandler;
  471. }
  472. public Widget getWidgetForPaintable() {
  473. return this;
  474. }
  475. }