Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600
  1. /*
  2. @ITMillApache2LicenseForJavaFiles@
  3. */
  4. //
  5. package com.vaadin.terminal.gwt.client.ui;
  6. import com.google.gwt.event.dom.client.KeyCodes;
  7. import com.google.gwt.user.client.Command;
  8. import com.google.gwt.user.client.DOM;
  9. import com.google.gwt.user.client.DeferredCommand;
  10. import com.google.gwt.user.client.Element;
  11. import com.google.gwt.user.client.Event;
  12. import com.google.gwt.user.client.Timer;
  13. import com.google.gwt.user.client.Window;
  14. import com.google.gwt.user.client.ui.HTML;
  15. import com.vaadin.terminal.gwt.client.ApplicationConnection;
  16. import com.vaadin.terminal.gwt.client.BrowserInfo;
  17. import com.vaadin.terminal.gwt.client.ContainerResizedListener;
  18. import com.vaadin.terminal.gwt.client.Paintable;
  19. import com.vaadin.terminal.gwt.client.UIDL;
  20. public class VSlider extends SimpleFocusablePanel implements Paintable, Field,
  21. ContainerResizedListener {
  22. public static final String CLASSNAME = "v-slider";
  23. /**
  24. * Minimum size (width or height, depending on orientation) of the slider
  25. * base.
  26. */
  27. private static final int MIN_SIZE = 50;
  28. ApplicationConnection client;
  29. String id;
  30. private boolean immediate;
  31. private boolean disabled;
  32. private boolean readonly;
  33. private boolean scrollbarStyle;
  34. private int acceleration = 1;
  35. private int handleSize;
  36. private double min;
  37. private double max;
  38. private int resolution;
  39. private Double value;
  40. private boolean vertical;
  41. private final int size = -1;
  42. private boolean arrows;
  43. private final HTML feedback = new HTML("", false);
  44. private final VOverlay feedbackPopup = new VOverlay(true, false, true) {
  45. @Override
  46. public void show() {
  47. super.show();
  48. updateFeedbackPosition();
  49. }
  50. };
  51. /* DOM element for slider's base */
  52. private final Element base;
  53. private final int BASE_BORDER_WIDTH = 1;
  54. /* DOM element for slider's handle */
  55. private final Element handle;
  56. /* DOM element for decrement arrow */
  57. private final Element smaller;
  58. /* DOM element for increment arrow */
  59. private final Element bigger;
  60. /* Temporary dragging/animation variables */
  61. private boolean dragging = false;
  62. public VSlider() {
  63. super();
  64. base = DOM.createDiv();
  65. handle = DOM.createDiv();
  66. smaller = DOM.createDiv();
  67. bigger = DOM.createDiv();
  68. setStyleName(CLASSNAME);
  69. DOM.setElementProperty(base, "className", CLASSNAME + "-base");
  70. DOM.setElementProperty(handle, "className", CLASSNAME + "-handle");
  71. DOM.setElementProperty(smaller, "className", CLASSNAME + "-smaller");
  72. DOM.setElementProperty(bigger, "className", CLASSNAME + "-bigger");
  73. DOM.appendChild(getElement(), bigger);
  74. DOM.appendChild(getElement(), smaller);
  75. DOM.appendChild(getElement(), base);
  76. DOM.appendChild(base, handle);
  77. // Hide initially
  78. DOM.setStyleAttribute(smaller, "display", "none");
  79. DOM.setStyleAttribute(bigger, "display", "none");
  80. DOM.setStyleAttribute(handle, "visibility", "hidden");
  81. DOM.sinkEvents(getElement(), Event.MOUSEEVENTS | Event.ONMOUSEWHEEL
  82. | Event.KEYEVENTS | Event.FOCUSEVENTS);
  83. DOM.sinkEvents(base, Event.ONCLICK);
  84. DOM.sinkEvents(handle, Event.MOUSEEVENTS);
  85. DOM.sinkEvents(smaller, Event.ONMOUSEDOWN | Event.ONMOUSEUP
  86. | Event.ONMOUSEOUT);
  87. DOM.sinkEvents(bigger, Event.ONMOUSEDOWN | Event.ONMOUSEUP
  88. | Event.ONMOUSEOUT);
  89. feedbackPopup.addStyleName(CLASSNAME + "-feedback");
  90. feedbackPopup.setWidget(feedback);
  91. }
  92. public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
  93. this.client = client;
  94. id = uidl.getId();
  95. // Ensure correct implementation
  96. if (client.updateComponent(this, uidl, true)) {
  97. return;
  98. }
  99. immediate = uidl.getBooleanAttribute("immediate");
  100. disabled = uidl.getBooleanAttribute("disabled");
  101. readonly = uidl.getBooleanAttribute("readonly");
  102. vertical = uidl.hasAttribute("vertical");
  103. arrows = uidl.hasAttribute("arrows");
  104. String style = "";
  105. if (uidl.hasAttribute("style")) {
  106. style = uidl.getStringAttribute("style");
  107. }
  108. scrollbarStyle = style.indexOf("scrollbar") > -1;
  109. if (arrows) {
  110. DOM.setStyleAttribute(smaller, "display", "block");
  111. DOM.setStyleAttribute(bigger, "display", "block");
  112. }
  113. if (vertical) {
  114. addStyleName(CLASSNAME + "-vertical");
  115. } else {
  116. removeStyleName(CLASSNAME + "-vertical");
  117. }
  118. min = uidl.getDoubleAttribute("min");
  119. max = uidl.getDoubleAttribute("max");
  120. resolution = uidl.getIntAttribute("resolution");
  121. value = new Double(uidl.getDoubleVariable("value"));
  122. setFeedbackValue(value);
  123. handleSize = uidl.getIntAttribute("hsize");
  124. buildBase();
  125. if (!vertical) {
  126. // Draw handle with a delay to allow base to gain maximum width
  127. DeferredCommand.addCommand(new Command() {
  128. public void execute() {
  129. buildHandle();
  130. setValue(value, false);
  131. }
  132. });
  133. } else {
  134. buildHandle();
  135. setValue(value, false);
  136. }
  137. }
  138. private void setFeedbackValue(double value) {
  139. String currentValue = "" + value;
  140. if (resolution == 0) {
  141. currentValue = "" + new Double(value).intValue();
  142. }
  143. feedback.setText(currentValue);
  144. }
  145. private void updateFeedbackPosition() {
  146. if (vertical) {
  147. feedbackPopup.setPopupPosition(
  148. DOM.getAbsoluteLeft(handle) + handle.getOffsetWidth(),
  149. DOM.getAbsoluteTop(handle) + handle.getOffsetHeight() / 2
  150. - feedbackPopup.getOffsetHeight() / 2);
  151. } else {
  152. feedbackPopup.setPopupPosition(
  153. DOM.getAbsoluteLeft(handle) + handle.getOffsetWidth() / 2
  154. - feedbackPopup.getOffsetWidth() / 2,
  155. DOM.getAbsoluteTop(handle)
  156. - feedbackPopup.getOffsetHeight());
  157. }
  158. }
  159. private void buildBase() {
  160. final String styleAttribute = vertical ? "height" : "width";
  161. final String domProperty = vertical ? "offsetHeight" : "offsetWidth";
  162. if (size == -1) {
  163. final Element p = DOM.getParent(getElement());
  164. if (DOM.getElementPropertyInt(p, domProperty) > 50) {
  165. if (vertical) {
  166. setHeight();
  167. } else {
  168. DOM.setStyleAttribute(base, styleAttribute, "");
  169. }
  170. } else {
  171. // Set minimum size and adjust after all components have
  172. // (supposedly) been drawn completely.
  173. DOM.setStyleAttribute(base, styleAttribute, MIN_SIZE + "px");
  174. DeferredCommand.addCommand(new Command() {
  175. public void execute() {
  176. final Element p = DOM.getParent(getElement());
  177. if (DOM.getElementPropertyInt(p, domProperty) > (MIN_SIZE + 5)) {
  178. if (vertical) {
  179. setHeight();
  180. } else {
  181. DOM.setStyleAttribute(base, styleAttribute, "");
  182. }
  183. // Ensure correct position
  184. setValue(value, false);
  185. }
  186. }
  187. });
  188. }
  189. } else {
  190. DOM.setStyleAttribute(base, styleAttribute, size + "px");
  191. }
  192. // TODO attach listeners for focusing and arrow keys
  193. }
  194. private void buildHandle() {
  195. final String styleAttribute = vertical ? "height" : "width";
  196. final String handleAttribute = vertical ? "marginTop" : "marginLeft";
  197. final String domProperty = vertical ? "offsetHeight" : "offsetWidth";
  198. DOM.setStyleAttribute(handle, handleAttribute, "0");
  199. if (scrollbarStyle) {
  200. // Only stretch the handle if scrollbar style is set.
  201. int s = (int) (Double.parseDouble(DOM.getElementProperty(base,
  202. domProperty)) / 100 * handleSize);
  203. if (handleSize == -1) {
  204. final int baseS = Integer.parseInt(DOM.getElementProperty(base,
  205. domProperty));
  206. final double range = (max - min) * (resolution + 1) * 3;
  207. s = (int) (baseS - range);
  208. }
  209. if (s < 3) {
  210. s = 3;
  211. }
  212. DOM.setStyleAttribute(handle, styleAttribute, s + "px");
  213. } else {
  214. DOM.setStyleAttribute(handle, styleAttribute, "");
  215. }
  216. // Restore visibility
  217. DOM.setStyleAttribute(handle, "visibility", "visible");
  218. }
  219. private void setValue(Double value, boolean updateToServer) {
  220. if (value == null) {
  221. return;
  222. }
  223. if (value.doubleValue() < min) {
  224. value = new Double(min);
  225. } else if (value.doubleValue() > max) {
  226. value = new Double(max);
  227. }
  228. // Update handle position
  229. final String styleAttribute = vertical ? "marginTop" : "marginLeft";
  230. final String domProperty = vertical ? "offsetHeight" : "offsetWidth";
  231. final int handleSize = Integer.parseInt(DOM.getElementProperty(handle,
  232. domProperty));
  233. final int baseSize = Integer.parseInt(DOM.getElementProperty(base,
  234. domProperty)) - (2 * BASE_BORDER_WIDTH);
  235. final int range = baseSize - handleSize;
  236. double v = value.doubleValue();
  237. // Round value to resolution
  238. if (resolution > 0) {
  239. v = Math.round(v * Math.pow(10, resolution));
  240. v = v / Math.pow(10, resolution);
  241. } else {
  242. v = Math.round(v);
  243. }
  244. final double valueRange = max - min;
  245. double p = 0;
  246. if (valueRange > 0) {
  247. p = range * ((v - min) / valueRange);
  248. }
  249. if (p < 0) {
  250. p = 0;
  251. }
  252. if (vertical) {
  253. // IE6 rounding behaves a little unstable, reduce one pixel so the
  254. // containing element (base) won't expand without limits
  255. p = range - p - (BrowserInfo.get().isIE6() ? 1 : 0);
  256. }
  257. final double pos = p;
  258. DOM.setStyleAttribute(handle, styleAttribute, (Math.round(pos)) + "px");
  259. // Update value
  260. this.value = new Double(v);
  261. setFeedbackValue(v);
  262. if (updateToServer) {
  263. updateValueToServer();
  264. }
  265. }
  266. @Override
  267. public void onBrowserEvent(Event event) {
  268. if (disabled || readonly) {
  269. return;
  270. }
  271. final Element targ = DOM.eventGetTarget(event);
  272. if (DOM.eventGetType(event) == Event.ONMOUSEWHEEL) {
  273. processMouseWheelEvent(event);
  274. } else if (dragging || targ == handle) {
  275. processHandleEvent(event);
  276. } else if (targ == smaller) {
  277. decreaseValue(true);
  278. } else if (targ == bigger) {
  279. increaseValue(true);
  280. } else if (DOM.eventGetType(event) == Event.MOUSEEVENTS) {
  281. processBaseEvent(event);
  282. } else if ((BrowserInfo.get().isGecko() && DOM.eventGetType(event) == Event.ONKEYPRESS)
  283. || (!BrowserInfo.get().isGecko() && DOM.eventGetType(event) == Event.ONKEYDOWN)) {
  284. if (handleNavigation(event.getKeyCode(), event.getCtrlKey(),
  285. event.getShiftKey())) {
  286. feedbackPopup.show();
  287. if (scrollTimer != null) {
  288. scrollTimer.cancel();
  289. }
  290. scrollTimer = new Timer() {
  291. @Override
  292. public void run() {
  293. updateValueToServer();
  294. acceleration = 1;
  295. }
  296. };
  297. scrollTimer.schedule(100);
  298. DOM.eventPreventDefault(event);
  299. DOM.eventCancelBubble(event, true);
  300. }
  301. } else if (targ.equals(getElement())
  302. && DOM.eventGetType(event) == Event.ONFOCUS) {
  303. feedbackPopup.show();
  304. } else if (targ.equals(getElement())
  305. && DOM.eventGetType(event) == Event.ONBLUR) {
  306. ApplicationConnection.getConsole().error(targ.getClassName());
  307. feedbackPopup.hide();
  308. } else if (DOM.eventGetType(event) == Event.ONMOUSEDOWN) {
  309. feedbackPopup.show();
  310. }
  311. }
  312. private Timer scrollTimer;
  313. private void processMouseWheelEvent(final Event event) {
  314. final int dir = DOM.eventGetMouseWheelVelocityY(event);
  315. if (dir < 0) {
  316. increaseValue(false);
  317. } else {
  318. decreaseValue(false);
  319. }
  320. if (scrollTimer != null) {
  321. scrollTimer.cancel();
  322. }
  323. scrollTimer = new Timer() {
  324. @Override
  325. public void run() {
  326. updateValueToServer();
  327. }
  328. };
  329. scrollTimer.schedule(100);
  330. DOM.eventPreventDefault(event);
  331. DOM.eventCancelBubble(event, true);
  332. }
  333. private void processHandleEvent(Event event) {
  334. switch (DOM.eventGetType(event)) {
  335. case Event.ONMOUSEDOWN:
  336. if (!disabled && !readonly) {
  337. focus();
  338. feedbackPopup.show();
  339. dragging = true;
  340. DOM.setElementProperty(handle, "className", CLASSNAME
  341. + "-handle " + CLASSNAME + "-handle-active");
  342. DOM.setCapture(getElement());
  343. DOM.eventPreventDefault(event); // prevent selecting text
  344. DOM.eventCancelBubble(event, true);
  345. }
  346. break;
  347. case Event.ONMOUSEMOVE:
  348. if (dragging) {
  349. setValueByEvent(event, false);
  350. updateFeedbackPosition();
  351. }
  352. break;
  353. case Event.ONMOUSEUP:
  354. // feedbackPopup.hide();
  355. dragging = false;
  356. DOM.setElementProperty(handle, "className", CLASSNAME + "-handle");
  357. DOM.releaseCapture(getElement());
  358. setValueByEvent(event, true);
  359. break;
  360. default:
  361. break;
  362. }
  363. }
  364. private void processBaseEvent(Event event) {
  365. if (DOM.eventGetType(event) == Event.ONMOUSEDOWN) {
  366. if (!disabled && !readonly && !dragging) {
  367. setValueByEvent(event, true);
  368. DOM.eventCancelBubble(event, true);
  369. }
  370. } else if (DOM.eventGetType(event) == Event.ONMOUSEDOWN && dragging) {
  371. dragging = false;
  372. DOM.releaseCapture(getElement());
  373. setValueByEvent(event, true);
  374. }
  375. }
  376. private void decreaseValue(boolean updateToServer) {
  377. setValue(new Double(value.doubleValue() - Math.pow(10, -resolution)),
  378. updateToServer);
  379. }
  380. private void increaseValue(boolean updateToServer) {
  381. setValue(new Double(value.doubleValue() + Math.pow(10, -resolution)),
  382. updateToServer);
  383. }
  384. private void setValueByEvent(Event event, boolean updateToServer) {
  385. double v = min; // Fallback to min
  386. final int coord = vertical ? DOM.eventGetClientY(event) : DOM
  387. .eventGetClientX(event);
  388. final String domProperty = vertical ? "offsetHeight" : "offsetWidth";
  389. final int handleSize = Integer.parseInt(DOM.getElementProperty(handle,
  390. domProperty));
  391. final double baseSize = Integer.parseInt(DOM.getElementProperty(base,
  392. domProperty));
  393. final double baseOffset = vertical ? DOM.getAbsoluteTop(base)
  394. - Window.getScrollTop() - handleSize / 2 : DOM
  395. .getAbsoluteLeft(base)
  396. - Window.getScrollLeft()
  397. + handleSize
  398. / 2;
  399. if (vertical) {
  400. v = ((baseSize - (coord - baseOffset)) / (baseSize - handleSize))
  401. * (max - min) + min;
  402. } else {
  403. v = ((coord - baseOffset) / (baseSize - handleSize)) * (max - min)
  404. + min;
  405. }
  406. if (v < min) {
  407. v = min;
  408. } else if (v > max) {
  409. v = max;
  410. }
  411. setValue(new Double(v), updateToServer);
  412. }
  413. public void iLayout() {
  414. if (vertical) {
  415. setHeight();
  416. }
  417. // Update handle position
  418. setValue(value, false);
  419. }
  420. private void setHeight() {
  421. if (size == -1) {
  422. // Calculate decoration size
  423. DOM.setStyleAttribute(base, "height", "0");
  424. DOM.setStyleAttribute(base, "overflow", "hidden");
  425. int h = DOM.getElementPropertyInt(getElement(), "offsetHeight");
  426. if (h < MIN_SIZE) {
  427. h = MIN_SIZE;
  428. }
  429. DOM.setStyleAttribute(base, "height", h + "px");
  430. } else {
  431. DOM.setStyleAttribute(base, "height", size + "px");
  432. }
  433. DOM.setStyleAttribute(base, "overflow", "");
  434. }
  435. private void updateValueToServer() {
  436. client.updateVariable(id, "value", value.doubleValue(), immediate);
  437. }
  438. /**
  439. * Handles the keyboard events handled by the Slider
  440. *
  441. * @param event
  442. * The keyboard event received
  443. * @return true iff the navigation event was handled
  444. */
  445. public boolean handleNavigation(int keycode, boolean ctrl, boolean shift) {
  446. // No support for ctrl moving
  447. if (ctrl) {
  448. return false;
  449. }
  450. if ((keycode == getNavigationUpKey() && vertical)
  451. || (keycode == getNavigationRightKey() && !vertical)) {
  452. if (shift) {
  453. for (int a = 0; a < acceleration; a++) {
  454. increaseValue(false);
  455. }
  456. acceleration++;
  457. } else {
  458. increaseValue(false);
  459. }
  460. return true;
  461. } else if (keycode == getNavigationDownKey() && vertical
  462. || (keycode == getNavigationLeftKey() && !vertical)) {
  463. if (shift) {
  464. for (int a = 0; a < acceleration; a++) {
  465. decreaseValue(false);
  466. }
  467. acceleration++;
  468. } else {
  469. decreaseValue(false);
  470. }
  471. return true;
  472. }
  473. return false;
  474. }
  475. /**
  476. * Get the key that increases the vertical slider. By default it is the up
  477. * arrow key but by overriding this you can change the key to whatever you
  478. * want.
  479. *
  480. * @return The keycode of the key
  481. */
  482. protected int getNavigationUpKey() {
  483. return KeyCodes.KEY_UP;
  484. }
  485. /**
  486. * Get the key that decreases the vertical slider. By default it is the down
  487. * arrow key but by overriding this you can change the key to whatever you
  488. * want.
  489. *
  490. * @return The keycode of the key
  491. */
  492. protected int getNavigationDownKey() {
  493. return KeyCodes.KEY_DOWN;
  494. }
  495. /**
  496. * Get the key that decreases the horizontal slider. By default it is the
  497. * left arrow key but by overriding this you can change the key to whatever
  498. * you want.
  499. *
  500. * @return The keycode of the key
  501. */
  502. protected int getNavigationLeftKey() {
  503. return KeyCodes.KEY_LEFT;
  504. }
  505. /**
  506. * Get the key that increases the horizontal slider. By default it is the
  507. * right arrow key but by overriding this you can change the key to whatever
  508. * you want.
  509. *
  510. * @return The keycode of the key
  511. */
  512. protected int getNavigationRightKey() {
  513. return KeyCodes.KEY_RIGHT;
  514. }
  515. }