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.

VSplitPanel.java 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584
  1. /*
  2. @ITMillApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.terminal.gwt.client.ui;
  5. import java.util.Set;
  6. import com.google.gwt.user.client.Command;
  7. import com.google.gwt.user.client.DOM;
  8. import com.google.gwt.user.client.DeferredCommand;
  9. import com.google.gwt.user.client.Element;
  10. import com.google.gwt.user.client.Event;
  11. import com.google.gwt.user.client.ui.ComplexPanel;
  12. import com.google.gwt.user.client.ui.RootPanel;
  13. import com.google.gwt.user.client.ui.Widget;
  14. import com.vaadin.terminal.gwt.client.ApplicationConnection;
  15. import com.vaadin.terminal.gwt.client.BrowserInfo;
  16. import com.vaadin.terminal.gwt.client.Container;
  17. import com.vaadin.terminal.gwt.client.ContainerResizedListener;
  18. import com.vaadin.terminal.gwt.client.Paintable;
  19. import com.vaadin.terminal.gwt.client.RenderInformation;
  20. import com.vaadin.terminal.gwt.client.RenderSpace;
  21. import com.vaadin.terminal.gwt.client.UIDL;
  22. import com.vaadin.terminal.gwt.client.Util;
  23. public class VSplitPanel extends ComplexPanel implements Container,
  24. ContainerResizedListener {
  25. public static final String CLASSNAME = "i-splitpanel";
  26. public static final int ORIENTATION_HORIZONTAL = 0;
  27. public static final int ORIENTATION_VERTICAL = 1;
  28. private static final int MIN_SIZE = 30;
  29. private int orientation = ORIENTATION_HORIZONTAL;
  30. private Widget firstChild;
  31. private Widget secondChild;
  32. private final Element wrapper = DOM.createDiv();
  33. private final Element firstContainer = DOM.createDiv();
  34. private final Element secondContainer = DOM.createDiv();
  35. private final Element splitter = DOM.createDiv();
  36. private boolean resizing;
  37. private int origX;
  38. private int origY;
  39. private int origMouseX;
  40. private int origMouseY;
  41. private boolean locked = false;
  42. private String[] componentStyleNames;
  43. private Element draggingCurtain;
  44. private ApplicationConnection client;
  45. private String width = "";
  46. private String height = "";
  47. private RenderSpace firstRenderSpace = new RenderSpace(0, 0, true);
  48. private RenderSpace secondRenderSpace = new RenderSpace(0, 0, true);
  49. RenderInformation renderInformation = new RenderInformation();
  50. private String id;
  51. private boolean immediate;
  52. private boolean rendering = false;
  53. public VSplitPanel() {
  54. this(ORIENTATION_HORIZONTAL);
  55. }
  56. public VSplitPanel(int orientation) {
  57. setElement(DOM.createDiv());
  58. switch (orientation) {
  59. case ORIENTATION_HORIZONTAL:
  60. setStyleName(CLASSNAME + "-horizontal");
  61. break;
  62. case ORIENTATION_VERTICAL:
  63. default:
  64. setStyleName(CLASSNAME + "-vertical");
  65. break;
  66. }
  67. // size below will be overridden in update from uidl, initial size
  68. // needed to keep IE alive
  69. setWidth(MIN_SIZE + "px");
  70. setHeight(MIN_SIZE + "px");
  71. constructDom();
  72. setOrientation(orientation);
  73. DOM.sinkEvents(splitter, (Event.MOUSEEVENTS));
  74. DOM.sinkEvents(getElement(), (Event.MOUSEEVENTS));
  75. }
  76. protected void constructDom() {
  77. DOM.appendChild(splitter, DOM.createDiv()); // for styling
  78. DOM.appendChild(getElement(), wrapper);
  79. DOM.setStyleAttribute(wrapper, "position", "relative");
  80. DOM.setStyleAttribute(wrapper, "width", "100%");
  81. DOM.setStyleAttribute(wrapper, "height", "100%");
  82. DOM.appendChild(wrapper, secondContainer);
  83. DOM.appendChild(wrapper, firstContainer);
  84. DOM.appendChild(wrapper, splitter);
  85. DOM.setStyleAttribute(splitter, "position", "absolute");
  86. DOM.setStyleAttribute(secondContainer, "position", "absolute");
  87. DOM.setStyleAttribute(firstContainer, "overflow", "auto");
  88. DOM.setStyleAttribute(secondContainer, "overflow", "auto");
  89. }
  90. private void setOrientation(int orientation) {
  91. this.orientation = orientation;
  92. if (orientation == ORIENTATION_HORIZONTAL) {
  93. DOM.setStyleAttribute(splitter, "height", "100%");
  94. DOM.setStyleAttribute(splitter, "top", "0");
  95. DOM.setStyleAttribute(firstContainer, "height", "100%");
  96. DOM.setStyleAttribute(secondContainer, "height", "100%");
  97. } else {
  98. DOM.setStyleAttribute(splitter, "width", "100%");
  99. DOM.setStyleAttribute(splitter, "left", "0");
  100. DOM.setStyleAttribute(firstContainer, "width", "100%");
  101. DOM.setStyleAttribute(secondContainer, "width", "100%");
  102. }
  103. DOM.setElementProperty(firstContainer, "className", CLASSNAME
  104. + "-first-container");
  105. DOM.setElementProperty(secondContainer, "className", CLASSNAME
  106. + "-second-container");
  107. }
  108. public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
  109. this.client = client;
  110. id = uidl.getId();
  111. rendering = true;
  112. immediate = uidl.hasAttribute("immediate");
  113. if (client.updateComponent(this, uidl, true)) {
  114. rendering = false;
  115. return;
  116. }
  117. if (uidl.hasAttribute("style")) {
  118. componentStyleNames = uidl.getStringAttribute("style").split(" ");
  119. } else {
  120. componentStyleNames = new String[0];
  121. }
  122. setLocked(uidl.getBooleanAttribute("locked"));
  123. setStylenames();
  124. setSplitPosition(uidl.getStringAttribute("position"));
  125. final Paintable newFirstChild = client.getPaintable(uidl
  126. .getChildUIDL(0));
  127. final Paintable newSecondChild = client.getPaintable(uidl
  128. .getChildUIDL(1));
  129. if (firstChild != newFirstChild) {
  130. if (firstChild != null) {
  131. client.unregisterPaintable((Paintable) firstChild);
  132. }
  133. setFirstWidget((Widget) newFirstChild);
  134. }
  135. if (secondChild != newSecondChild) {
  136. if (secondChild != null) {
  137. client.unregisterPaintable((Paintable) secondChild);
  138. }
  139. setSecondWidget((Widget) newSecondChild);
  140. }
  141. newFirstChild.updateFromUIDL(uidl.getChildUIDL(0), client);
  142. newSecondChild.updateFromUIDL(uidl.getChildUIDL(1), client);
  143. renderInformation.updateSize(getElement());
  144. if (BrowserInfo.get().isIE7()) {
  145. // Part III of IE7 hack
  146. DeferredCommand.addCommand(new Command() {
  147. public void execute() {
  148. iLayout();
  149. }
  150. });
  151. }
  152. rendering = false;
  153. }
  154. private void setLocked(boolean newValue) {
  155. if (locked != newValue) {
  156. locked = newValue;
  157. splitterSize = -1;
  158. setStylenames();
  159. }
  160. }
  161. private void setSplitPosition(String pos) {
  162. if (orientation == ORIENTATION_HORIZONTAL) {
  163. DOM.setStyleAttribute(splitter, "left", pos);
  164. } else {
  165. DOM.setStyleAttribute(splitter, "top", pos);
  166. }
  167. iLayout();
  168. client.runDescendentsLayout(this);
  169. }
  170. /*
  171. * Calculates absolutely positioned container places/sizes (non-Javadoc)
  172. *
  173. * @see com.vaadin.terminal.gwt.client.NeedsLayout#layout()
  174. */
  175. public void iLayout() {
  176. if (!isAttached()) {
  177. return;
  178. }
  179. renderInformation.updateSize(getElement());
  180. int wholeSize;
  181. int pixelPosition;
  182. switch (orientation) {
  183. case ORIENTATION_HORIZONTAL:
  184. wholeSize = DOM.getElementPropertyInt(wrapper, "clientWidth");
  185. pixelPosition = DOM.getElementPropertyInt(splitter, "offsetLeft");
  186. // reposition splitter in case it is out of box
  187. if (pixelPosition > 0
  188. && pixelPosition + getSplitterSize() > wholeSize) {
  189. pixelPosition = wholeSize - getSplitterSize();
  190. if (pixelPosition < 0) {
  191. pixelPosition = 0;
  192. }
  193. setSplitPosition(pixelPosition + "px");
  194. return;
  195. }
  196. DOM
  197. .setStyleAttribute(firstContainer, "width", pixelPosition
  198. + "px");
  199. int secondContainerWidth = (wholeSize - pixelPosition - getSplitterSize());
  200. if (secondContainerWidth < 0) {
  201. secondContainerWidth = 0;
  202. }
  203. DOM.setStyleAttribute(secondContainer, "width",
  204. secondContainerWidth + "px");
  205. DOM.setStyleAttribute(secondContainer, "left",
  206. (pixelPosition + getSplitterSize()) + "px");
  207. int contentHeight = renderInformation.getRenderedSize().getHeight();
  208. firstRenderSpace.setHeight(contentHeight);
  209. firstRenderSpace.setWidth(pixelPosition);
  210. secondRenderSpace.setHeight(contentHeight);
  211. secondRenderSpace.setWidth(secondContainerWidth);
  212. break;
  213. case ORIENTATION_VERTICAL:
  214. wholeSize = DOM.getElementPropertyInt(wrapper, "clientHeight");
  215. pixelPosition = DOM.getElementPropertyInt(splitter, "offsetTop");
  216. // reposition splitter in case it is out of box
  217. if (pixelPosition > 0
  218. && pixelPosition + getSplitterSize() > wholeSize) {
  219. pixelPosition = wholeSize - getSplitterSize();
  220. if (pixelPosition < 0) {
  221. pixelPosition = 0;
  222. }
  223. setSplitPosition(pixelPosition + "px");
  224. return;
  225. }
  226. DOM.setStyleAttribute(firstContainer, "height", pixelPosition
  227. + "px");
  228. int secondContainerHeight = (wholeSize - pixelPosition - getSplitterSize());
  229. if (secondContainerHeight < 0) {
  230. secondContainerHeight = 0;
  231. }
  232. DOM.setStyleAttribute(secondContainer, "height",
  233. secondContainerHeight + "px");
  234. DOM.setStyleAttribute(secondContainer, "top",
  235. (pixelPosition + getSplitterSize()) + "px");
  236. int contentWidth = renderInformation.getRenderedSize().getWidth();
  237. firstRenderSpace.setHeight(pixelPosition);
  238. firstRenderSpace.setWidth(contentWidth);
  239. secondRenderSpace.setHeight(secondContainerHeight);
  240. secondRenderSpace.setWidth(contentWidth);
  241. break;
  242. }
  243. // fixes scrollbars issues on webkit based browsers
  244. Util.runWebkitOverflowAutoFix(secondContainer);
  245. Util.runWebkitOverflowAutoFix(firstContainer);
  246. }
  247. private void setFirstWidget(Widget w) {
  248. if (firstChild != null) {
  249. firstChild.removeFromParent();
  250. }
  251. super.add(w, firstContainer);
  252. firstChild = w;
  253. }
  254. private void setSecondWidget(Widget w) {
  255. if (secondChild != null) {
  256. secondChild.removeFromParent();
  257. }
  258. super.add(w, secondContainer);
  259. secondChild = w;
  260. }
  261. @Override
  262. public void onBrowserEvent(Event event) {
  263. switch (DOM.eventGetType(event)) {
  264. case Event.ONMOUSEMOVE:
  265. if (resizing) {
  266. onMouseMove(event);
  267. }
  268. break;
  269. case Event.ONMOUSEDOWN:
  270. onMouseDown(event);
  271. break;
  272. case Event.ONMOUSEUP:
  273. if (resizing) {
  274. onMouseUp(event);
  275. }
  276. break;
  277. case Event.ONCLICK:
  278. resizing = false;
  279. break;
  280. }
  281. }
  282. public void onMouseDown(Event event) {
  283. if (locked) {
  284. return;
  285. }
  286. final Element trg = DOM.eventGetTarget(event);
  287. if (trg == splitter || trg == DOM.getChild(splitter, 0)) {
  288. resizing = true;
  289. if (BrowserInfo.get().isGecko()) {
  290. showDraggingCurtain();
  291. }
  292. DOM.setCapture(getElement());
  293. origX = DOM.getElementPropertyInt(splitter, "offsetLeft");
  294. origY = DOM.getElementPropertyInt(splitter, "offsetTop");
  295. origMouseX = DOM.eventGetClientX(event);
  296. origMouseY = DOM.eventGetClientY(event);
  297. DOM.eventCancelBubble(event, true);
  298. DOM.eventPreventDefault(event);
  299. }
  300. }
  301. public void onMouseMove(Event event) {
  302. switch (orientation) {
  303. case ORIENTATION_HORIZONTAL:
  304. final int x = DOM.eventGetClientX(event);
  305. onHorizontalMouseMove(x);
  306. break;
  307. case ORIENTATION_VERTICAL:
  308. default:
  309. final int y = DOM.eventGetClientY(event);
  310. onVerticalMouseMove(y);
  311. break;
  312. }
  313. iLayout();
  314. // TODO Check if this is needed
  315. client.runDescendentsLayout(this);
  316. }
  317. private void onHorizontalMouseMove(int x) {
  318. int newX = origX + x - origMouseX;
  319. if (newX < 0) {
  320. newX = 0;
  321. }
  322. if (newX + getSplitterSize() > getOffsetWidth()) {
  323. newX = getOffsetWidth() - getSplitterSize();
  324. }
  325. DOM.setStyleAttribute(splitter, "left", newX + "px");
  326. updateSplitPosition(newX);
  327. }
  328. private void onVerticalMouseMove(int y) {
  329. int newY = origY + y - origMouseY;
  330. if (newY < 0) {
  331. newY = 0;
  332. }
  333. if (newY + getSplitterSize() > getOffsetHeight()) {
  334. newY = getOffsetHeight() - getSplitterSize();
  335. }
  336. DOM.setStyleAttribute(splitter, "top", newY + "px");
  337. updateSplitPosition(newY);
  338. }
  339. public void onMouseUp(Event event) {
  340. DOM.releaseCapture(getElement());
  341. if (BrowserInfo.get().isGecko()) {
  342. hideDraggingCurtain();
  343. }
  344. resizing = false;
  345. onMouseMove(event);
  346. }
  347. /**
  348. * Used in FF to avoid losing mouse capture when pointer is moved on an
  349. * iframe.
  350. */
  351. private void showDraggingCurtain() {
  352. if (draggingCurtain == null) {
  353. draggingCurtain = DOM.createDiv();
  354. DOM.setStyleAttribute(draggingCurtain, "position", "absolute");
  355. DOM.setStyleAttribute(draggingCurtain, "top", "0px");
  356. DOM.setStyleAttribute(draggingCurtain, "left", "0px");
  357. DOM.setStyleAttribute(draggingCurtain, "width", "100%");
  358. DOM.setStyleAttribute(draggingCurtain, "height", "100%");
  359. DOM.setStyleAttribute(draggingCurtain, "zIndex", ""
  360. + VToolkitOverlay.Z_INDEX);
  361. DOM.appendChild(RootPanel.getBodyElement(), draggingCurtain);
  362. }
  363. }
  364. /**
  365. * Hides dragging curtain
  366. */
  367. private void hideDraggingCurtain() {
  368. if (draggingCurtain != null) {
  369. DOM.removeChild(RootPanel.getBodyElement(), draggingCurtain);
  370. draggingCurtain = null;
  371. }
  372. }
  373. private int splitterSize = -1;
  374. private int getSplitterSize() {
  375. if (splitterSize < 0) {
  376. if (isAttached()) {
  377. switch (orientation) {
  378. case ORIENTATION_HORIZONTAL:
  379. splitterSize = DOM.getElementPropertyInt(splitter,
  380. "offsetWidth");
  381. break;
  382. default:
  383. splitterSize = DOM.getElementPropertyInt(splitter,
  384. "offsetHeight");
  385. break;
  386. }
  387. }
  388. }
  389. return splitterSize;
  390. }
  391. @Override
  392. public void setHeight(String height) {
  393. if (this.height.equals(height)) {
  394. return;
  395. }
  396. this.height = height;
  397. super.setHeight(height);
  398. if (!rendering && client != null) {
  399. iLayout();
  400. client.runDescendentsLayout(this);
  401. }
  402. }
  403. @Override
  404. public void setWidth(String width) {
  405. if (this.width.equals(width)) {
  406. return;
  407. }
  408. this.width = width;
  409. super.setWidth(width);
  410. if (!rendering && client != null) {
  411. iLayout();
  412. client.runDescendentsLayout(this);
  413. }
  414. }
  415. public RenderSpace getAllocatedSpace(Widget child) {
  416. if (child == firstChild) {
  417. return firstRenderSpace;
  418. } else if (child == secondChild) {
  419. return secondRenderSpace;
  420. }
  421. return null;
  422. }
  423. public boolean hasChildComponent(Widget component) {
  424. return (component != null && (component == firstChild || component == secondChild));
  425. }
  426. public void replaceChildComponent(Widget oldComponent, Widget newComponent) {
  427. if (oldComponent == firstChild) {
  428. setFirstWidget(newComponent);
  429. } else if (oldComponent == secondChild) {
  430. setSecondWidget(newComponent);
  431. }
  432. }
  433. public boolean requestLayout(Set<Paintable> child) {
  434. if (height != null && width != null) {
  435. /*
  436. * If the height and width has been specified the child components
  437. * cannot make the size of the layout change
  438. */
  439. return true;
  440. }
  441. if (renderInformation.updateSize(getElement())) {
  442. return false;
  443. } else {
  444. return true;
  445. }
  446. }
  447. public void updateCaption(Paintable component, UIDL uidl) {
  448. // TODO Implement caption handling
  449. }
  450. /**
  451. * Updates the new split position back to server.
  452. *
  453. * @param pos
  454. * The new position of the split handle.
  455. */
  456. private void updateSplitPosition(int pos) {
  457. // We always send pixel values to server
  458. client.updateVariable(id, "position", pos, immediate);
  459. }
  460. private void setStylenames() {
  461. final String splitterSuffix = (orientation == ORIENTATION_HORIZONTAL ? "-hsplitter"
  462. : "-vsplitter");
  463. final String firstContainerSuffix = "-first-container";
  464. final String secondContainerSuffix = "-second-container";
  465. String lockedSuffix = "";
  466. String splitterStyle = CLASSNAME + splitterSuffix;
  467. String firstStyle = CLASSNAME + firstContainerSuffix;
  468. String secondStyle = CLASSNAME + secondContainerSuffix;
  469. if (locked) {
  470. splitterStyle = CLASSNAME + splitterSuffix + "-locked";
  471. lockedSuffix = "-locked";
  472. }
  473. for (int i = 0; i < componentStyleNames.length; i++) {
  474. splitterStyle += " " + CLASSNAME + splitterSuffix + "-"
  475. + componentStyleNames[i] + lockedSuffix;
  476. firstStyle += " " + CLASSNAME + firstContainerSuffix + "-"
  477. + componentStyleNames[i];
  478. secondStyle += " " + CLASSNAME + secondContainerSuffix + "-"
  479. + componentStyleNames[i];
  480. }
  481. DOM.setElementProperty(splitter, "className", splitterStyle);
  482. DOM.setElementProperty(firstContainer, "className", firstStyle);
  483. DOM.setElementProperty(secondContainer, "className", secondStyle);
  484. }
  485. }