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

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