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.

IPanel.java 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  1. /*
  2. @ITMillApache2LicenseForJavaFiles@
  3. */
  4. package com.itmill.toolkit.terminal.gwt.client.ui;
  5. import java.util.Set;
  6. import com.google.gwt.user.client.DOM;
  7. import com.google.gwt.user.client.Element;
  8. import com.google.gwt.user.client.Event;
  9. import com.google.gwt.user.client.ui.SimplePanel;
  10. import com.google.gwt.user.client.ui.Widget;
  11. import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;
  12. import com.itmill.toolkit.terminal.gwt.client.BrowserInfo;
  13. import com.itmill.toolkit.terminal.gwt.client.Container;
  14. import com.itmill.toolkit.terminal.gwt.client.ContainerResizedListener;
  15. import com.itmill.toolkit.terminal.gwt.client.IErrorMessage;
  16. import com.itmill.toolkit.terminal.gwt.client.Paintable;
  17. import com.itmill.toolkit.terminal.gwt.client.RenderInformation;
  18. import com.itmill.toolkit.terminal.gwt.client.RenderSpace;
  19. import com.itmill.toolkit.terminal.gwt.client.UIDL;
  20. import com.itmill.toolkit.terminal.gwt.client.Util;
  21. public class IPanel extends SimplePanel implements Container,
  22. ContainerResizedListener {
  23. public static final String CLASSNAME = "i-panel";
  24. ApplicationConnection client;
  25. String id;
  26. private final Element captionNode = DOM.createDiv();
  27. private final Element captionText = DOM.createSpan();
  28. private Icon icon;
  29. private final Element bottomDecoration = DOM.createDiv();
  30. private final Element contentNode = DOM.createDiv();
  31. private Element errorIndicatorElement;
  32. private IErrorMessage errorMessage;
  33. private String height;
  34. private Paintable layout;
  35. ShortcutActionHandler shortcutHandler;
  36. private String width;
  37. private Element geckoCaptionMeter;
  38. private int scrollTop;
  39. private int scrollLeft;
  40. private RenderInformation renderInformation = new RenderInformation();
  41. private int borderPaddingHorizontal = -1;
  42. private int borderPaddingVertical = -1;
  43. public IPanel() {
  44. super();
  45. DOM.appendChild(getElement(), captionNode);
  46. DOM.appendChild(captionNode, captionText);
  47. DOM.appendChild(getElement(), contentNode);
  48. DOM.appendChild(getElement(), bottomDecoration);
  49. setStyleName(CLASSNAME);
  50. DOM
  51. .setElementProperty(captionNode, "className", CLASSNAME
  52. + "-caption");
  53. DOM
  54. .setElementProperty(contentNode, "className", CLASSNAME
  55. + "-content");
  56. DOM.setElementProperty(bottomDecoration, "className", CLASSNAME
  57. + "-deco");
  58. DOM.sinkEvents(getElement(), Event.ONKEYDOWN);
  59. DOM.sinkEvents(contentNode, Event.ONSCROLL);
  60. }
  61. @Override
  62. protected Element getContainerElement() {
  63. return contentNode;
  64. }
  65. private void setCaption(String text) {
  66. DOM.setInnerText(captionText, text);
  67. }
  68. public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
  69. // Ensure correct implementation
  70. if (client.updateComponent(this, uidl, false)) {
  71. return;
  72. }
  73. this.client = client;
  74. id = uidl.getId();
  75. // Restore default stylenames
  76. DOM
  77. .setElementProperty(captionNode, "className", CLASSNAME
  78. + "-caption");
  79. DOM
  80. .setElementProperty(contentNode, "className", CLASSNAME
  81. + "-content");
  82. DOM.setElementProperty(bottomDecoration, "className", CLASSNAME
  83. + "-deco");
  84. // Handle caption displaying
  85. boolean hasCaption = false;
  86. if (uidl.hasAttribute("caption")
  87. && !uidl.getStringAttribute("caption").equals("")) {
  88. setCaption(uidl.getStringAttribute("caption"));
  89. hasCaption = true;
  90. } else {
  91. setCaption("");
  92. DOM.setElementProperty(captionNode, "className", CLASSNAME
  93. + "-nocaption");
  94. }
  95. setIconUri(uidl, client);
  96. handleDescription(uidl);
  97. handleError(uidl);
  98. // Add proper stylenames for all elements. This way we can prevent
  99. // unwanted CSS selector inheritance.
  100. if (uidl.hasAttribute("style")) {
  101. final String[] styles = uidl.getStringAttribute("style").split(" ");
  102. final String captionBaseClass = CLASSNAME
  103. + (hasCaption ? "-caption" : "-nocaption");
  104. final String contentBaseClass = CLASSNAME + "-content";
  105. final String decoBaseClass = CLASSNAME + "-deco";
  106. String captionClass = captionBaseClass;
  107. String contentClass = contentBaseClass;
  108. String decoClass = decoBaseClass;
  109. for (int i = 0; i < styles.length; i++) {
  110. captionClass += " " + captionBaseClass + "-" + styles[i];
  111. contentClass += " " + contentBaseClass + "-" + styles[i];
  112. decoClass += " " + decoBaseClass + "-" + styles[i];
  113. }
  114. DOM.setElementProperty(captionNode, "className", captionClass);
  115. DOM.setElementProperty(contentNode, "className", contentClass);
  116. DOM.setElementProperty(bottomDecoration, "className", decoClass);
  117. }
  118. // Height adjustment
  119. iLayout(false);
  120. // Render content
  121. final UIDL layoutUidl = uidl.getChildUIDL(0);
  122. final Paintable newLayout = client.getPaintable(layoutUidl);
  123. if (newLayout != layout) {
  124. if (layout != null) {
  125. client.unregisterPaintable(layout);
  126. }
  127. setWidget((Widget) newLayout);
  128. layout = newLayout;
  129. }
  130. (layout).updateFromUIDL(layoutUidl, client);
  131. // We may have actions attached to this panel
  132. if (uidl.getChildCount() > 1) {
  133. final int cnt = uidl.getChildCount();
  134. for (int i = 1; i < cnt; i++) {
  135. UIDL childUidl = uidl.getChildUIDL(i);
  136. if (childUidl.getTag().equals("actions")) {
  137. if (shortcutHandler == null) {
  138. shortcutHandler = new ShortcutActionHandler(id, client);
  139. }
  140. shortcutHandler.updateActionMap(childUidl);
  141. }
  142. }
  143. }
  144. if (uidl.hasVariable("scrollTop")
  145. && uidl.getIntVariable("scrollTop") != scrollTop) {
  146. scrollTop = uidl.getIntVariable("scrollTop");
  147. DOM.setElementPropertyInt(contentNode, "scrollTop", scrollTop);
  148. }
  149. if (uidl.hasVariable("scrollLeft")
  150. && uidl.getIntVariable("scrollLeft") != scrollLeft) {
  151. scrollLeft = uidl.getIntVariable("scrollLeft");
  152. DOM.setElementPropertyInt(contentNode, "scrollLeft", scrollLeft);
  153. }
  154. }
  155. private void handleError(UIDL uidl) {
  156. if (uidl.hasAttribute("error")) {
  157. final UIDL errorUidl = uidl.getErrors();
  158. if (errorIndicatorElement == null) {
  159. errorIndicatorElement = DOM.createDiv();
  160. DOM.setElementProperty(errorIndicatorElement, "className",
  161. "i-errorindicator");
  162. DOM.sinkEvents(errorIndicatorElement, Event.MOUSEEVENTS);
  163. sinkEvents(Event.MOUSEEVENTS);
  164. }
  165. DOM.insertBefore(captionNode, errorIndicatorElement, captionText);
  166. if (errorMessage == null) {
  167. errorMessage = new IErrorMessage();
  168. }
  169. errorMessage.updateFromUIDL(errorUidl);
  170. } else if (errorIndicatorElement != null) {
  171. DOM.removeChild(captionNode, errorIndicatorElement);
  172. errorIndicatorElement = null;
  173. }
  174. }
  175. private void handleDescription(UIDL uidl) {
  176. DOM.setElementProperty(captionText, "title", uidl
  177. .hasAttribute("description") ? uidl
  178. .getStringAttribute("description") : "");
  179. }
  180. private void setIconUri(UIDL uidl, ApplicationConnection client) {
  181. final String iconUri = uidl.hasAttribute("icon") ? uidl
  182. .getStringAttribute("icon") : null;
  183. if (iconUri == null) {
  184. if (icon != null) {
  185. DOM.removeChild(captionNode, icon.getElement());
  186. icon = null;
  187. }
  188. } else {
  189. if (icon == null) {
  190. icon = new Icon(client);
  191. DOM.insertChild(captionNode, icon.getElement(), 0);
  192. }
  193. icon.setUri(iconUri);
  194. }
  195. }
  196. public void iLayout() {
  197. iLayout(true);
  198. }
  199. public void iLayout(boolean runGeckoFix) {
  200. renderInformation.updateSize(getElement());
  201. if (BrowserInfo.get().isIE6() && width != null && !width.equals("")) {
  202. /*
  203. * IE6 requires overflow-hidden elements to have a width specified
  204. */
  205. /*
  206. * Fixes #1923 IPanel: Horizontal scrollbar does not appear in IE6
  207. * with wide content
  208. */
  209. /*
  210. * Caption must be shrunk for parent measurements to return correct
  211. * result in IE6
  212. */
  213. DOM.setStyleAttribute(captionNode, "width", "1px");
  214. int parentPadding = Util.measureHorizontalPadding(getElement(), 0);
  215. int parentWidthExcludingPadding = getElement().getOffsetWidth()
  216. - parentPadding;
  217. int captionMarginLeft = captionNode.getAbsoluteLeft()
  218. - getElement().getAbsoluteLeft();
  219. Util.setWidthExcludingPadding(captionNode,
  220. parentWidthExcludingPadding - captionMarginLeft, 26);
  221. int contentMarginLeft = contentNode.getAbsoluteLeft()
  222. - getElement().getAbsoluteLeft();
  223. Util.setWidthExcludingPadding(contentNode,
  224. parentWidthExcludingPadding - contentMarginLeft, 2);
  225. }
  226. if (runGeckoFix && BrowserInfo.get().isGecko()) {
  227. // workaround for #1764
  228. if (width == null || width.equals("")) {
  229. if (geckoCaptionMeter == null) {
  230. geckoCaptionMeter = DOM.createDiv();
  231. DOM.appendChild(captionNode, geckoCaptionMeter);
  232. }
  233. int captionWidth = DOM.getElementPropertyInt(captionText,
  234. "offsetWidth");
  235. int availWidth = DOM.getElementPropertyInt(geckoCaptionMeter,
  236. "offsetWidth");
  237. if (captionWidth == availWidth) {
  238. /*
  239. * Caption width defines panel width -> Gecko based browsers
  240. * somehow fails to float things right, without the
  241. * "noncode" below
  242. */
  243. setWidth(getOffsetWidth() + "px");
  244. } else {
  245. DOM.setStyleAttribute(captionNode, "width", "");
  246. }
  247. }
  248. }
  249. client.runDescendentsLayout(this);
  250. Util.runWebkitOverflowAutoFix(contentNode);
  251. }
  252. @Override
  253. public void onBrowserEvent(Event event) {
  254. final Element target = DOM.eventGetTarget(event);
  255. final int type = DOM.eventGetType(event);
  256. if (type == Event.ONKEYDOWN && shortcutHandler != null) {
  257. shortcutHandler.handleKeyboardEvent(event);
  258. return;
  259. }
  260. if (type == Event.ONSCROLL) {
  261. int newscrollTop = DOM.getElementPropertyInt(contentNode,
  262. "scrollTop");
  263. int newscrollLeft = DOM.getElementPropertyInt(contentNode,
  264. "scrollLeft");
  265. if (client != null
  266. && (newscrollLeft != scrollLeft || newscrollTop != scrollTop)) {
  267. scrollLeft = newscrollLeft;
  268. scrollTop = newscrollTop;
  269. client.updateVariable(id, "scrollTop", scrollTop, false);
  270. client.updateVariable(id, "scrollLeft", scrollLeft, false);
  271. }
  272. } else if (errorIndicatorElement != null
  273. && target == errorIndicatorElement) {
  274. switch (type) {
  275. case Event.ONMOUSEOVER:
  276. if (errorMessage != null) {
  277. errorMessage.showAt(errorIndicatorElement);
  278. }
  279. break;
  280. case Event.ONMOUSEOUT:
  281. if (errorMessage != null) {
  282. errorMessage.hide();
  283. }
  284. break;
  285. case Event.ONCLICK:
  286. ApplicationConnection.getConsole().log(
  287. DOM.getInnerHTML(errorMessage.getElement()));
  288. return;
  289. default:
  290. break;
  291. }
  292. }
  293. }
  294. @Override
  295. public void setHeight(String height) {
  296. this.height = height;
  297. super.setHeight(height);
  298. if (height != null && height != "") {
  299. final int targetHeight = getOffsetHeight();
  300. int containerHeight = targetHeight - captionNode.getOffsetHeight()
  301. - bottomDecoration.getOffsetHeight()
  302. - getContainerBorderHeight();
  303. if (containerHeight < 0) {
  304. containerHeight = 0;
  305. }
  306. DOM
  307. .setStyleAttribute(contentNode, "height", containerHeight
  308. + "px");
  309. } else {
  310. DOM.setStyleAttribute(contentNode, "height", "");
  311. }
  312. }
  313. private int getContainerBorderHeight() {
  314. if (borderPaddingVertical < 0) {
  315. detectContainerBorders();
  316. }
  317. return borderPaddingVertical;
  318. }
  319. @Override
  320. public void setWidth(String width) {
  321. this.width = width;
  322. super.setWidth(width);
  323. }
  324. private int getContainerBorderWidth() {
  325. if (borderPaddingHorizontal < 0) {
  326. detectContainerBorders();
  327. }
  328. return borderPaddingHorizontal;
  329. }
  330. private void detectContainerBorders() {
  331. DOM.setStyleAttribute(contentNode, "overflow", "hidden");
  332. borderPaddingHorizontal = contentNode.getOffsetWidth()
  333. - contentNode.getPropertyInt("clientWidth");
  334. assert borderPaddingHorizontal >= 0;
  335. borderPaddingVertical = contentNode.getOffsetHeight()
  336. - contentNode.getPropertyInt("clientHeight");
  337. assert borderPaddingVertical >= 0;
  338. DOM.setStyleAttribute(contentNode, "overflow", "auto");
  339. }
  340. public boolean hasChildComponent(Widget component) {
  341. if (component != null && component == layout) {
  342. return true;
  343. } else {
  344. return false;
  345. }
  346. }
  347. public void replaceChildComponent(Widget oldComponent, Widget newComponent) {
  348. // TODO
  349. }
  350. private RenderSpace contentNodeSize;
  351. public RenderSpace getAllocatedSpace(Widget child) {
  352. if (contentNodeSize == null) {
  353. contentNodeSize = new RenderSpace(-1, -1) {
  354. @Override
  355. public int getHeight() {
  356. return contentNode.getOffsetHeight()
  357. - getContainerBorderHeight();
  358. }
  359. @Override
  360. public int getWidth() {
  361. return contentNode.getOffsetWidth()
  362. - getContainerBorderWidth();
  363. }
  364. @Override
  365. public int getScrollbarSize() {
  366. return Util.getNativeScrollbarSize();
  367. }
  368. };
  369. }
  370. return contentNodeSize;
  371. }
  372. public boolean requestLayout(Set<Paintable> child) {
  373. if (height != null && width != null) {
  374. /*
  375. * If the height and width has been specified the child components
  376. * cannot make the size of the layout change
  377. */
  378. return true;
  379. }
  380. return !renderInformation.updateSize(getElement());
  381. }
  382. public void updateCaption(Paintable component, UIDL uidl) {
  383. // NOP: layouts caption, errors etc not rendered in Panel
  384. }
  385. }