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.

AbstractComponentConnector.java 32KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898
  1. /*
  2. * Copyright 2000-2016 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.client.ui;
  17. import com.google.gwt.core.client.GWT;
  18. import com.google.gwt.core.client.JsArrayString;
  19. import com.google.gwt.dom.client.Element;
  20. import com.google.gwt.dom.client.EventTarget;
  21. import com.google.gwt.dom.client.Touch;
  22. import com.google.gwt.event.dom.client.ContextMenuEvent;
  23. import com.google.gwt.event.dom.client.ContextMenuHandler;
  24. import com.google.gwt.event.dom.client.TouchEndEvent;
  25. import com.google.gwt.event.dom.client.TouchEndHandler;
  26. import com.google.gwt.event.dom.client.TouchMoveEvent;
  27. import com.google.gwt.event.dom.client.TouchMoveHandler;
  28. import com.google.gwt.event.dom.client.TouchStartEvent;
  29. import com.google.gwt.event.dom.client.TouchStartHandler;
  30. import com.google.gwt.event.shared.HandlerRegistration;
  31. import com.google.gwt.user.client.Timer;
  32. import com.google.gwt.user.client.ui.Focusable;
  33. import com.google.gwt.user.client.ui.HasEnabled;
  34. import com.google.gwt.user.client.ui.Widget;
  35. import com.vaadin.client.BrowserInfo;
  36. import com.vaadin.client.ComponentConnector;
  37. import com.vaadin.client.HasComponentsConnector;
  38. import com.vaadin.client.LayoutManager;
  39. import com.vaadin.client.MouseEventDetailsBuilder;
  40. import com.vaadin.client.Profiler;
  41. import com.vaadin.client.ServerConnector;
  42. import com.vaadin.client.StyleConstants;
  43. import com.vaadin.client.TooltipInfo;
  44. import com.vaadin.client.UIDL;
  45. import com.vaadin.client.Util;
  46. import com.vaadin.client.VConsole;
  47. import com.vaadin.client.WidgetUtil;
  48. import com.vaadin.client.WidgetUtil.ErrorUtil;
  49. import com.vaadin.client.annotations.OnStateChange;
  50. import com.vaadin.client.communication.StateChangeEvent;
  51. import com.vaadin.client.extensions.DragSourceExtensionConnector;
  52. import com.vaadin.client.extensions.DropTargetExtensionConnector;
  53. import com.vaadin.client.metadata.NoDataException;
  54. import com.vaadin.client.metadata.Type;
  55. import com.vaadin.client.metadata.TypeData;
  56. import com.vaadin.client.ui.ui.UIConnector;
  57. import com.vaadin.shared.AbstractComponentState;
  58. import com.vaadin.shared.ComponentConstants;
  59. import com.vaadin.shared.Connector;
  60. import com.vaadin.shared.ContextClickRpc;
  61. import com.vaadin.shared.EventId;
  62. import com.vaadin.shared.MouseEventDetails;
  63. import com.vaadin.shared.ui.ComponentStateUtil;
  64. import com.vaadin.shared.ui.TabIndexState;
  65. import com.vaadin.shared.ui.ui.UIState;
  66. public abstract class AbstractComponentConnector extends AbstractConnector
  67. implements ComponentConnector, HasErrorIndicator {
  68. private HandlerRegistration contextHandler = null;
  69. private Widget widget;
  70. private String lastKnownWidth = "";
  71. private String lastKnownHeight = "";
  72. private boolean tooltipListenersAttached = false;
  73. /**
  74. * The style names from getState().getStyles() which are currently applied
  75. * to the widget.
  76. */
  77. private JsArrayString styleNames = JsArrayString.createArray().cast();
  78. private Timer longTouchTimer;
  79. // TODO encapsulate into a nested class
  80. private HandlerRegistration touchStartHandler;
  81. private HandlerRegistration touchMoveHandler;
  82. private HandlerRegistration touchEndHandler;
  83. private int touchStartX;
  84. private int touchStartY;
  85. private boolean preventNextTouchEnd = false;
  86. protected int SIGNIFICANT_MOVE_THRESHOLD = 20; // pixels
  87. // long touch event delay
  88. // TODO replace with global constant for accessibility
  89. private static final int TOUCH_CONTEXT_MENU_TIMEOUT = 500;
  90. /**
  91. * Default constructor
  92. */
  93. public AbstractComponentConnector() {
  94. }
  95. @OnStateChange("registeredEventListeners")
  96. void handleContextClickListenerChange() {
  97. if (contextHandler == null && hasEventListener(EventId.CONTEXT_CLICK)) {
  98. contextHandler = getWidget()
  99. .addDomHandler(new ContextMenuHandler() {
  100. @Override
  101. public void onContextMenu(ContextMenuEvent event) {
  102. final MouseEventDetails mouseEventDetails = MouseEventDetailsBuilder
  103. .buildMouseEventDetails(
  104. event.getNativeEvent(),
  105. getWidget().getElement());
  106. event.preventDefault();
  107. event.stopPropagation();
  108. sendContextClickEvent(mouseEventDetails,
  109. event.getNativeEvent().getEventTarget());
  110. }
  111. }, ContextMenuEvent.getType());
  112. // if the widget has a contextclick listener, add touch support as
  113. // well.
  114. if (shouldHandleLongTap()) {
  115. registerTouchHandlers();
  116. }
  117. } else if (contextHandler != null
  118. && !hasEventListener(EventId.CONTEXT_CLICK)) {
  119. contextHandler.removeHandler();
  120. contextHandler = null;
  121. // remove the touch handlers as well
  122. unregisterTouchHandlers();
  123. }
  124. }
  125. /**
  126. * The new default behaviour is for long taps to fire a contextclick event
  127. * if there's a contextclick listener attached to the component.
  128. *
  129. * If you do not want this in your component, override this with a blank
  130. * method to get rid of said behaviour.
  131. *
  132. * @since 7.6
  133. */
  134. protected void unregisterTouchHandlers() {
  135. if (touchStartHandler != null) {
  136. touchStartHandler.removeHandler();
  137. touchStartHandler = null;
  138. }
  139. if (touchMoveHandler != null) {
  140. touchMoveHandler.removeHandler();
  141. touchMoveHandler = null;
  142. }
  143. if (touchEndHandler != null) {
  144. touchEndHandler.removeHandler();
  145. touchEndHandler = null;
  146. }
  147. }
  148. /**
  149. * The new default behaviour is for long taps to fire a contextclick event
  150. * if there's a contextclick listener attached to the component.
  151. *
  152. * If you do not want this in your component, override this with a blank
  153. * method to get rid of said behaviour.
  154. *
  155. * Some Vaadin Components already handle the long tap as a context menu.
  156. * This method is unnecessary for those.
  157. *
  158. * @since 7.6
  159. */
  160. protected void registerTouchHandlers() {
  161. touchStartHandler = getWidget().addDomHandler(new TouchStartHandler() {
  162. @Override
  163. public void onTouchStart(final TouchStartEvent event) {
  164. if (longTouchTimer != null && longTouchTimer.isRunning()) {
  165. return;
  166. }
  167. // Prevent selection for the element while pending long tap.
  168. WidgetUtil.setTextSelectionEnabled(getWidget().getElement(),
  169. false);
  170. if (BrowserInfo.get().isAndroid()) {
  171. // Android fires ContextMenu events automatically.
  172. return;
  173. }
  174. /*
  175. * we need to build mouseEventDetails eagerly - the event won't
  176. * be guaranteed to be around when the timer executes. At least
  177. * this was the case with iOS devices.
  178. */
  179. final MouseEventDetails mouseEventDetails = MouseEventDetailsBuilder
  180. .buildMouseEventDetails(event.getNativeEvent(),
  181. getWidget().getElement());
  182. final EventTarget eventTarget = event.getNativeEvent()
  183. .getEventTarget();
  184. longTouchTimer = new Timer() {
  185. @Override
  186. public void run() {
  187. // we're handling this event, our parent components
  188. // don't need to bother with it anymore.
  189. cancelParentTouchTimers();
  190. // The default context click
  191. // implementation only provides the
  192. // mouse coordinates relative to root
  193. // element of widget.
  194. sendContextClickEvent(mouseEventDetails, eventTarget);
  195. preventNextTouchEnd = true;
  196. }
  197. };
  198. Touch touch = event.getChangedTouches().get(0);
  199. touchStartX = touch.getClientX();
  200. touchStartY = touch.getClientY();
  201. longTouchTimer.schedule(TOUCH_CONTEXT_MENU_TIMEOUT);
  202. }
  203. }, TouchStartEvent.getType());
  204. touchMoveHandler = getWidget().addDomHandler(new TouchMoveHandler() {
  205. @Override
  206. public void onTouchMove(TouchMoveEvent event) {
  207. if (isSignificantMove(event)) {
  208. // Moved finger before the context menu timer
  209. // expired, so let the browser handle the event.
  210. cancelTouchTimer();
  211. }
  212. }
  213. // mostly copy-pasted code from VScrollTable
  214. // TODO refactor main logic to a common class
  215. private boolean isSignificantMove(TouchMoveEvent event) {
  216. if (longTouchTimer == null) {
  217. // no touch start
  218. return false;
  219. }
  220. // Calculate the distance between touch start and the current
  221. // touch
  222. // position
  223. Touch touch = event.getChangedTouches().get(0);
  224. int deltaX = touch.getClientX() - touchStartX;
  225. int deltaY = touch.getClientY() - touchStartY;
  226. int delta = deltaX * deltaX + deltaY * deltaY;
  227. // Compare to the square of the significant move threshold to
  228. // remove the need for a square root
  229. if (delta > SIGNIFICANT_MOVE_THRESHOLD
  230. * SIGNIFICANT_MOVE_THRESHOLD) {
  231. return true;
  232. }
  233. return false;
  234. }
  235. }, TouchMoveEvent.getType());
  236. touchEndHandler = getWidget().addDomHandler(new TouchEndHandler() {
  237. @Override
  238. public void onTouchEnd(TouchEndEvent event) {
  239. // cancel the timer so the event doesn't fire
  240. cancelTouchTimer();
  241. if (preventNextTouchEnd) {
  242. event.preventDefault();
  243. preventNextTouchEnd = false;
  244. }
  245. }
  246. }, TouchEndEvent.getType());
  247. }
  248. protected boolean shouldHandleLongTap() {
  249. return BrowserInfo.get().isTouchDevice();
  250. }
  251. /**
  252. * If a long touch event timer is running, cancel it.
  253. *
  254. * @since 7.6
  255. */
  256. private void cancelTouchTimer() {
  257. WidgetUtil.setTextSelectionEnabled(getWidget().getElement(), true);
  258. if (longTouchTimer != null) {
  259. // Re-enable text selection
  260. longTouchTimer.cancel();
  261. }
  262. }
  263. /**
  264. * Cancel the timer recursively for parent components that have timers
  265. * running
  266. *
  267. * @since 7.6
  268. */
  269. private void cancelParentTouchTimers() {
  270. ServerConnector parent = getParent();
  271. // we have to account for the parent being something other than an
  272. // abstractcomponent. getParent returns null for the root element.
  273. while (parent != null) {
  274. if (parent instanceof AbstractComponentConnector) {
  275. ((AbstractComponentConnector) parent).cancelTouchTimer();
  276. }
  277. parent = parent.getParent();
  278. }
  279. }
  280. /**
  281. * This method sends the context menu event to the server-side. Can be
  282. * overridden to provide extra information through an alternative RPC
  283. * interface.
  284. *
  285. * @since 7.6
  286. * @param details
  287. * @param eventTarget
  288. */
  289. protected void sendContextClickEvent(MouseEventDetails details,
  290. EventTarget eventTarget) {
  291. // The default context click implementation only provides the mouse
  292. // coordinates relative to root element of widget.
  293. getRpcProxy(ContextClickRpc.class).contextClick(details);
  294. WidgetUtil.clearTextSelection();
  295. }
  296. /**
  297. * Creates and returns the widget for this VPaintableWidget. This method
  298. * should only be called once when initializing the paintable.
  299. * <p>
  300. * You should typically not override this method since the framework by
  301. * default generates an implementation that uses {@link GWT#create(Class)}
  302. * to create a widget of the same type as returned by the most specific
  303. * override of {@link #getWidget()}. If you do override the method, you
  304. * can't call <code>super.createWidget()</code> since the metadata needed
  305. * for that implementation is not generated if there's an override of the
  306. * method.
  307. *
  308. * @return a new widget instance to use for this component connector
  309. */
  310. protected Widget createWidget() {
  311. Type type = TypeData.getType(getClass());
  312. try {
  313. Type widgetType = type.getMethod("getWidget").getReturnType();
  314. Object instance = widgetType.createInstance();
  315. return (Widget) instance;
  316. } catch (NoDataException e) {
  317. throw new IllegalStateException(
  318. "Default implementation of createWidget() does not work for "
  319. + getClass().getSimpleName()
  320. + ". This might be caused by explicitely using "
  321. + "super.createWidget() or some unspecified "
  322. + "problem with the widgetset compilation.",
  323. e);
  324. }
  325. }
  326. /**
  327. * Returns the widget associated with this paintable. The widget returned by
  328. * this method must not changed during the life time of the paintable.
  329. *
  330. * @return The widget associated with this paintable
  331. */
  332. @Override
  333. public Widget getWidget() {
  334. if (widget == null) {
  335. if (Profiler.isEnabled()) {
  336. Profiler.enter("AbstractComponentConnector.createWidget for "
  337. + getClass().getSimpleName());
  338. }
  339. widget = createWidget();
  340. if (Profiler.isEnabled()) {
  341. Profiler.leave("AbstractComponentConnector.createWidget for "
  342. + getClass().getSimpleName());
  343. }
  344. }
  345. return widget;
  346. }
  347. @Deprecated
  348. public static boolean isRealUpdate(UIDL uidl) {
  349. return !uidl.hasAttribute("cached");
  350. }
  351. @Override
  352. public AbstractComponentState getState() {
  353. return (AbstractComponentState) super.getState();
  354. }
  355. @Override
  356. public void onStateChanged(StateChangeEvent stateChangeEvent) {
  357. Profiler.enter("AbstractComponentConnector.onStateChanged");
  358. Profiler.enter("AbstractComponentConnector.onStateChanged update id");
  359. if (stateChangeEvent.hasPropertyChanged("id")) {
  360. if (getState().id != null) {
  361. getWidget().getElement().setId(getState().id);
  362. } else if (!stateChangeEvent.isInitialStateChange()) {
  363. getWidget().getElement().removeAttribute("id");
  364. }
  365. }
  366. Profiler.leave("AbstractComponentConnector.onStateChanged update id");
  367. /*
  368. * Disabled state may affect (override) tabindex so the order must be
  369. * first setting tabindex, then enabled state (through super
  370. * implementation).
  371. */
  372. Profiler.enter(
  373. "AbstractComponentConnector.onStateChanged update tab index");
  374. if (getState() instanceof TabIndexState) {
  375. if (getWidget() instanceof Focusable) {
  376. ((Focusable) getWidget())
  377. .setTabIndex(((TabIndexState) getState()).tabIndex);
  378. } else {
  379. /*
  380. * TODO Enable this error when all widgets have been fixed to
  381. * properly support tabIndex, i.e. implement Focusable
  382. */
  383. // VConsole.error("Tab index received for "
  384. // + Util.getSimpleName(getWidget())
  385. // + " which does not implement Focusable");
  386. }
  387. } else if (getState() instanceof UIState
  388. && getWidget() instanceof Focusable) {
  389. // UI behaves like a component with TabIndexState
  390. ((Focusable) getWidget())
  391. .setTabIndex(((UIState) getState()).tabIndex);
  392. }
  393. Profiler.leave(
  394. "AbstractComponentConnector.onStateChanged update tab index");
  395. Profiler.enter(
  396. "AbstractComponentConnector.onStateChanged AbstractConnector.onStateChanged()");
  397. super.onStateChanged(stateChangeEvent);
  398. Profiler.leave(
  399. "AbstractComponentConnector.onStateChanged AbstractConnector.onStateChanged()");
  400. // Style names
  401. Profiler.enter(
  402. "AbstractComponentConnector.onStateChanged updateWidgetStyleNames");
  403. updateWidgetStyleNames();
  404. Profiler.leave(
  405. "AbstractComponentConnector.onStateChanged updateWidgetStyleNames");
  406. /*
  407. * updateComponentSize need to be after caption update so caption can be
  408. * taken into account
  409. */
  410. Profiler.enter(
  411. "AbstractComponentConnector.onStateChanged updateComponentSize");
  412. updateComponentSize();
  413. Profiler.leave(
  414. "AbstractComponentConnector.onStateChanged updateComponentSize");
  415. Profiler.enter(
  416. "AbstractComponentContainer.onStateChanged check tooltip");
  417. if (!tooltipListenersAttached && hasTooltip()) {
  418. /*
  419. * Add event handlers for tooltips if they are needed but have not
  420. * yet been added.
  421. */
  422. tooltipListenersAttached = true;
  423. getConnection().getVTooltip().connectHandlersToWidget(getWidget());
  424. }
  425. Profiler.leave(
  426. "AbstractComponentContainer.onStateChanged check tooltip");
  427. Profiler.leave("AbstractComponentConnector.onStateChanged");
  428. }
  429. @OnStateChange({"errorMessage", "errorLevel"})
  430. private void setErrorLevel() {
  431. // Add or remove the widget's error level style name
  432. ErrorUtil.setErrorLevelStyle(getWidget().getElement(),
  433. getWidget().getStylePrimaryName() + StyleConstants.ERROR_EXT,
  434. getState().errorLevel);
  435. // Add or remove error indicator element
  436. if (getWidget() instanceof HasErrorIndicatorElement) {
  437. HasErrorIndicatorElement widget = (HasErrorIndicatorElement) getWidget();
  438. if (getState().errorMessage != null) {
  439. widget.setErrorIndicatorElementVisible(true);
  440. ErrorUtil.setErrorLevelStyle(widget.getErrorIndicatorElement(),
  441. StyleConstants.STYLE_NAME_ERROR_INDICATOR,
  442. getState().errorLevel);
  443. } else {
  444. widget.setErrorIndicatorElementVisible(false);
  445. }
  446. }
  447. }
  448. @Override
  449. public void setWidgetEnabled(boolean widgetEnabled) {
  450. // add or remove v-disabled style name from the widget
  451. setWidgetStyleName(StyleConstants.DISABLED, !widgetEnabled);
  452. if (getWidget() instanceof HasEnabled) {
  453. // set widget specific enabled state
  454. ((HasEnabled) getWidget()).setEnabled(widgetEnabled);
  455. }
  456. // make sure the caption has or has not v-disabled style
  457. if (delegateCaptionHandling()) {
  458. ServerConnector parent = getParent();
  459. if (parent instanceof HasComponentsConnector) {
  460. ((HasComponentsConnector) parent).updateCaption(this);
  461. } else if (parent == null && !(this instanceof UIConnector)) {
  462. VConsole.error("Parent of connector "
  463. + Util.getConnectorString(this)
  464. + " is null. This is typically an indication of a broken component hierarchy");
  465. }
  466. }
  467. }
  468. /**
  469. * Updates the component size based on the shared state, invoking the
  470. * {@link LayoutManager layout manager} if necessary.
  471. */
  472. protected void updateComponentSize() {
  473. updateComponentSize(getState().width == null ? "" : getState().width,
  474. getState().height == null ? "" : getState().height);
  475. }
  476. /**
  477. * Updates the component size, invoking the {@link LayoutManager layout
  478. * manager} if necessary.
  479. *
  480. * @param newWidth
  481. * The new width as a CSS string. Cannot be null.
  482. * @param newHeight
  483. * The new height as a CSS string. Cannot be null.
  484. */
  485. protected void updateComponentSize(String newWidth, String newHeight) {
  486. Profiler.enter("AbstractComponentConnector.updateComponentSize");
  487. // Parent should be updated if either dimension changed between relative
  488. // and non-relative
  489. if (newWidth.endsWith("%") != lastKnownWidth.endsWith("%")) {
  490. Connector parent = getParent();
  491. if (parent instanceof ManagedLayout) {
  492. getLayoutManager()
  493. .setNeedsHorizontalLayout((ManagedLayout) parent);
  494. }
  495. }
  496. if (newHeight.endsWith("%") != lastKnownHeight.endsWith("%")) {
  497. Connector parent = getParent();
  498. if (parent instanceof ManagedLayout) {
  499. getLayoutManager()
  500. .setNeedsVerticalLayout((ManagedLayout) parent);
  501. }
  502. }
  503. lastKnownWidth = newWidth;
  504. lastKnownHeight = newHeight;
  505. // Set defined sizes
  506. Widget widget = getWidget();
  507. Profiler.enter(
  508. "AbstractComponentConnector.updateComponentSize update styleNames");
  509. widget.setStyleName("v-has-width", !isUndefinedWidth());
  510. widget.setStyleName("v-has-height", !isUndefinedHeight());
  511. Profiler.leave(
  512. "AbstractComponentConnector.updateComponentSize update styleNames");
  513. Profiler.enter(
  514. "AbstractComponentConnector.updateComponentSize update DOM");
  515. updateWidgetSize(newWidth, newHeight);
  516. Profiler.leave(
  517. "AbstractComponentConnector.updateComponentSize update DOM");
  518. Profiler.leave("AbstractComponentConnector.updateComponentSize");
  519. }
  520. /**
  521. * Updates the DOM size of this connector's {@link #getWidget() widget}.
  522. *
  523. * @since 7.1.15
  524. * @param newWidth
  525. * The new width as a CSS string. Cannot be null.
  526. * @param newHeight
  527. * The new height as a CSS string. Cannot be null.
  528. */
  529. protected void updateWidgetSize(String newWidth, String newHeight) {
  530. getWidget().setWidth(newWidth);
  531. getWidget().setHeight(newHeight);
  532. }
  533. @Override
  534. public boolean isRelativeHeight() {
  535. return ComponentStateUtil.isRelativeHeight(getState());
  536. }
  537. @Override
  538. public boolean isRelativeWidth() {
  539. return ComponentStateUtil.isRelativeWidth(getState());
  540. }
  541. @Override
  542. public boolean isUndefinedHeight() {
  543. return ComponentStateUtil.isUndefinedHeight(getState());
  544. }
  545. @Override
  546. public boolean isUndefinedWidth() {
  547. return ComponentStateUtil.isUndefinedWidth(getState());
  548. }
  549. /*
  550. * (non-Javadoc)
  551. *
  552. * @see com.vaadin.client.ComponentConnector#delegateCaptionHandling ()
  553. */
  554. @Override
  555. public boolean delegateCaptionHandling() {
  556. return true;
  557. }
  558. /**
  559. * Updates the user defined, read-only and error style names for the widget
  560. * based the shared state. User defined style names are prefixed with the
  561. * primary style name of the widget returned by {@link #getWidget()}
  562. * <p>
  563. * This method can be overridden to provide additional style names for the
  564. * component, for example see {@code AbstractFieldConnector}
  565. * </p>
  566. */
  567. protected void updateWidgetStyleNames() {
  568. Profiler.enter("AbstractComponentConnector.updateWidgetStyleNames");
  569. AbstractComponentState state = getState();
  570. String primaryStyleName = getWidget().getStylePrimaryName();
  571. // Set the core 'v' style name for the widget
  572. setWidgetStyleName(StyleConstants.UI_WIDGET, true);
  573. // add / remove error style name
  574. setWidgetStyleNameWithPrefix(primaryStyleName, StyleConstants.ERROR_EXT,
  575. null != state.errorMessage);
  576. // add additional user defined style names as class names, prefixed with
  577. // component default class name. remove nonexistent style names.
  578. // Remove all old stylenames
  579. for (int i = 0; i < styleNames.length(); i++) {
  580. String oldStyle = styleNames.get(i);
  581. setWidgetStyleName(oldStyle, false);
  582. setWidgetStyleNameWithPrefix(primaryStyleName + "-", oldStyle,
  583. false);
  584. }
  585. styleNames.setLength(0);
  586. if (ComponentStateUtil.hasStyles(state)) {
  587. // add new style names
  588. for (String newStyle : state.styles) {
  589. setWidgetStyleName(newStyle, true);
  590. setWidgetStyleNameWithPrefix(primaryStyleName + "-", newStyle,
  591. true);
  592. styleNames.push(newStyle);
  593. }
  594. }
  595. if (state.primaryStyleName != null
  596. && !state.primaryStyleName.equals(primaryStyleName)) {
  597. /*
  598. * We overwrite the widgets primary stylename if state defines a
  599. * primary stylename. This has to be done after updating other
  600. * styles to be sure the dependent styles are updated correctly.
  601. */
  602. getWidget().setStylePrimaryName(state.primaryStyleName);
  603. }
  604. // set required style name if components supports that
  605. if (this instanceof HasRequiredIndicator) {
  606. getWidget().setStyleName(StyleConstants.REQUIRED,
  607. ((HasRequiredIndicator) this).isRequiredIndicatorVisible());
  608. }
  609. Profiler.leave("AbstractComponentConnector.updateWidgetStyleNames");
  610. }
  611. /**
  612. * This is used to add / remove state related style names from the widget.
  613. * <p>
  614. * Override this method for example if the style name given here should be
  615. * updated in another widget in addition to the one returned by the
  616. * {@link #getWidget()}.
  617. * </p>
  618. *
  619. * @param styleName
  620. * the style name to be added or removed
  621. * @param add
  622. * <code>true</code> to add the given style, <code>false</code>
  623. * to remove it
  624. */
  625. protected void setWidgetStyleName(String styleName, boolean add) {
  626. getWidget().setStyleName(styleName, add);
  627. }
  628. /**
  629. * This is used to add / remove state related prefixed style names from the
  630. * widget.
  631. * <p>
  632. * Override this method if the prefixed style name given here should be
  633. * updated in another widget in addition to the one returned by the
  634. * <code>Connector</code>'s {@link #getWidget()}, or if the prefix should be
  635. * different. For example see
  636. * {@link com.vaadin.client.ui.datefield.TextualDateConnector#setWidgetStyleNameWithPrefix(String, String, boolean)}
  637. * </p>
  638. *
  639. * @param styleName
  640. * the style name to be added or removed
  641. * @param add
  642. * <code>true</code> to add the given style, <code>false</code>
  643. * to remove it
  644. * @deprecated This will be removed once styles are no longer added with
  645. * prefixes.
  646. */
  647. @Deprecated
  648. protected void setWidgetStyleNameWithPrefix(String prefix, String styleName,
  649. boolean add) {
  650. if (!styleName.startsWith("-")) {
  651. if (!prefix.endsWith("-")) {
  652. prefix += "-";
  653. }
  654. } else {
  655. if (prefix.endsWith("-")) {
  656. styleName.replaceFirst("-", "");
  657. }
  658. }
  659. getWidget().setStyleName(prefix + styleName, add);
  660. }
  661. @Override
  662. public LayoutManager getLayoutManager() {
  663. return LayoutManager.get(getConnection());
  664. }
  665. @Override
  666. public void updateEnabledState(boolean enabledState) {
  667. super.updateEnabledState(enabledState);
  668. setWidgetEnabled(isEnabled());
  669. }
  670. @Override
  671. public void onUnregister() {
  672. super.onUnregister();
  673. // Show an error if widget is still attached to DOM. It should never be
  674. // at this point.
  675. if (getWidget() != null && getWidget().isAttached()) {
  676. getWidget().removeFromParent();
  677. VConsole.error(
  678. "Widget is still attached to the DOM after the connector ("
  679. + Util.getConnectorString(this)
  680. + ") has been unregistered. Widget was removed.");
  681. }
  682. }
  683. @Override
  684. public TooltipInfo getTooltipInfo(Element element) {
  685. return new TooltipInfo(getState().description,
  686. getState().descriptionContentMode, getState().errorMessage,
  687. null, getState().errorLevel);
  688. }
  689. @Override
  690. public boolean hasTooltip() {
  691. // Normally, there is a tooltip if description or errorMessage is set
  692. AbstractComponentState state = getState();
  693. if (state.description != null && !state.description.isEmpty()) {
  694. return true;
  695. }
  696. return state.errorMessage != null && !state.errorMessage.isEmpty();
  697. }
  698. /**
  699. * Gets the URI of the icon set for this component.
  700. *
  701. * @return the URI of the icon, or <code>null</code> if no icon has been
  702. * defined.
  703. */
  704. protected String getIconUri() {
  705. return getResourceUrl(ComponentConstants.ICON_RESOURCE);
  706. }
  707. /**
  708. * Gets the icon set for this component.
  709. *
  710. * @return the icon, or <code>null</code> if no icon has been defined.
  711. */
  712. protected Icon getIcon() {
  713. return getConnection().getIcon(getIconUri());
  714. }
  715. /*
  716. * (non-Javadoc)
  717. *
  718. * @see com.vaadin.client.ComponentConnector#flush()
  719. */
  720. @Override
  721. public void flush() {
  722. // No generic implementation. Override if needed
  723. }
  724. @Override
  725. public boolean isErrorIndicatorVisible() {
  726. return getState().errorMessage != null;
  727. }
  728. /**
  729. * Invoked when a {@link DragSourceExtensionConnector} has been attached to
  730. * this component.
  731. * <p>
  732. * By default, does nothing. If you need to apply some changes to the
  733. * widget, override this method.
  734. * <p>
  735. * This is a framework internal method, and should not be invoked manually.
  736. *
  737. * @since 8.1
  738. * @see #onDragSourceDetached()
  739. */
  740. public void onDragSourceAttached() {
  741. }
  742. /**
  743. * Invoked when a {@link DragSourceExtensionConnector} has been removed from
  744. * this component.
  745. * <p>
  746. * By default, does nothing.
  747. * <p>
  748. * This is a framework internal method, and should not be invoked manually.
  749. *
  750. * @since 8.1
  751. * @see #onDragSourceAttached()
  752. */
  753. public void onDragSourceDetached() {
  754. }
  755. /**
  756. * Invoked when a {@link DropTargetExtensionConnector} has been attached to
  757. * this component.
  758. * <p>
  759. * By default, does nothing. If you need to apply some changes to the
  760. * widget, override this method.
  761. * <p>
  762. * This is a framework internal method, and should not be invoked manually.
  763. *
  764. * @since 8.1
  765. * @see #onDropTargetDetached()
  766. */
  767. public void onDropTargetAttached() {
  768. }
  769. /**
  770. * Invoked when a {@link DropTargetExtensionConnector} has been removed from
  771. * this component.
  772. * <p>
  773. * By default, does nothing.
  774. * <p>
  775. * This is a framework internal method, and should not be invoked manually.
  776. *
  777. * @since 8.1
  778. * @see #onDropTargetAttached()
  779. */
  780. public void onDropTargetDetached() {
  781. }
  782. }